Browse Source

merge from release_v4.1.1

deason 3 years ago
parent
commit
21af741a87
61 changed files with 2349 additions and 1294 deletions
  1. 3 5
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataBeanConvert.java
  2. 0 23
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataDomain.java
  3. 63 64
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/IllegallyTypeDomain.java
  4. 35 15
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamAuditController.java
  5. 44 28
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordController.java
  6. 0 1
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordPaperStructController.java
  7. 17 146
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java
  8. 68 23
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamStudentController.java
  9. 43 186
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/OfflineExamController.java
  10. 0 4
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/PracticeController.java
  11. 228 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/client/ExamProcessController.java
  12. 16 2
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordCloudServiceProvider.java
  13. 89 0
      examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncExamDataCloudServiceProvider.java
  14. 42 22
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/Constants.java
  15. 33 0
      examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/RoleUtil.java
  16. 27 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureCameraInfoRepo.java
  17. 7 5
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureQueueRepo.java
  18. 15 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamFaceLiveVerifyRepo.java
  19. 4 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamProcessRecordRepo.java
  20. 10 1
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataRepo.java
  21. 113 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureCameraInfoEntity.java
  22. 6 9
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureEntity.java
  23. 66 64
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureQueueEntity.java
  24. 141 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamFaceLiveVerifyEntity.java
  25. 5 8
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamSyncCaptureEntity.java
  26. 1 1
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExportTaskEntity.java
  27. 34 30
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/DisciplineType.java
  28. 0 41
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/FaceBiopsyScheme.java
  29. 6 2
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/FaceBiopsyType.java
  30. 28 0
      examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/FaceLiveVerifyStatus.java
  31. 6 8
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamAuditService.java
  32. 2 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamProcessRecordService.java
  33. 0 6
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordForMarkingService.java
  34. 22 21
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordService.java
  35. 4 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamScoreService.java
  36. 11 7
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamStudentService.java
  37. 2 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExportTaskService.java
  38. 6 10
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/OfflineExamService.java
  39. 33 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/UserDataRules.java
  40. 11 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordQuery.java
  41. 129 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/uploadfile/FileInfo.java
  42. 84 19
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/AsyncExportServiceImpl.java
  43. 121 41
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamAuditServiceImpl.java
  44. 40 57
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamCaptureServiceImpl.java
  45. 23 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamProcessRecordServiceImpl.java
  46. 7 2
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataServiceImpl.java
  47. 6 3
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataSyncServiceImpl.java
  48. 0 28
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordForMarkingServiceImpl.java
  49. 3 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordPaperStructServiceImpl.java
  50. 2 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordQuestionsServiceImpl.java
  51. 232 198
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordServiceImpl.java
  52. 0 4
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreQueueServiceImpl.java
  53. 176 37
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreServiceImpl.java
  54. 90 33
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentServiceImpl.java
  55. 10 0
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExportTaskServiceImpl.java
  56. 134 107
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/OfflineExamServiceImpl.java
  57. 9 11
      examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/PracticeServiceImpl.java
  58. 5 5
      examcloud-core-oe-admin-starter/shell/start.sh
  59. 2 1
      examcloud-core-oe-admin-starter/shell/stop.sh
  60. 17 13
      examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/config/SwaggerConfig.java
  61. 18 0
      examcloud-core-oe-admin-starter/src/test/java/cn/com/qmth/examcloud/core/oe/admin/test/OeAdminTest.java

+ 3 - 5
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataBeanConvert.java

@@ -8,6 +8,7 @@
 package cn.com.qmth.examcloud.core.oe.admin.api.bean;
 
 import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
 
 /**
@@ -17,15 +18,13 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
  * @since: 2018/8/17
  */
 public class ExamRecordDataBeanConvert implements JsonSerializable {
-
     /**
      * 将数据库实体转换为业务实体
-     *
      * @param erdEntity
      * @return
      */
-    public static ExamRecordDataDomain copyFrom(ExamRecordDataEntity erdEntity) {
-        if (erdEntity == null) {
+    public static ExamRecordDataDomain copyFrom(ExamRecordDataEntity erdEntity){
+        if (erdEntity==null){
             return null;
         }
 
@@ -59,5 +58,4 @@ public class ExamRecordDataBeanConvert implements JsonSerializable {
         result.setWarn(erdEntity.getIsWarn());
         return result;
     }
-
 }

+ 0 - 23
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/ExamRecordDataDomain.java

@@ -16,47 +16,38 @@ import java.util.Date;
  * @Version 1.0
  */
 public class ExamRecordDataDomain implements JsonSerializable {
-
     private static final long serialVersionUID = 5819718857965384297L;
-
     private Long id;
 
     /**
      * 考试作答记录id
      */
     private String examRecordQuestionsId;
-
     /**
      * 考试记录状态(考试中,考试结束,考试过期,考试作废)
      */
     @Enumerated(EnumType.STRING)
     private ExamRecordStatus examRecordStatus;
-
     /**
      * 考试开始时间
      */
     private Date startTime;
-
     /**
      * 考试结束时间
      */
     private Date endTime;
-
     /**
      * 考试被清理时间
      */
     private Date cleanTime;
-
     /**
      * 是否异常数据
      */
     private Boolean isWarn;
-
     /**
      * 是否被审核过
      */
     private Boolean isAudit;
-
     /**
      * 是否违纪
      */
@@ -66,62 +57,50 @@ public class ExamRecordDataDomain implements JsonSerializable {
      * 考试时长
      */
     private Long usedExamTime;
-
     /**
      * 第几次考试
      */
     private Integer examOrder;
-
     /**
      * 是否为重考
      */
     private Boolean isReexamine;
-
     /**
      * 是否断点续考
      */
     private Boolean isContinued;
-
     /**
      * 是否是全客观题卷  1:是   0:否
      */
     private Boolean isAllObjectivePaper;
-
     /**
      * 断点续考次数
      */
     private Integer continuedCount;
-
     /**
      * 是否达到最大断点限制
      */
     private Boolean isExceed;
-
     /**
      * 抓拍比对成功次数
      */
     private Integer faceSuccessCount;
-
     /**
      * 抓拍比对失败次数
      */
     private Integer faceFailedCount;
-
     /**
      * 抓拍存在陌生人的次数
      */
     private Integer faceStrangerCount;
-
     /**
      * 抓拍比对总次数
      */
     private Integer faceTotalCount;
-
     /**
      * 抓拍比对成功比率
      */
     private Double faceSuccessPercent;
-
     /**
      * @see cn.com.qmth.examcloud.core.oe.admin.dao.enums.IsSuccess
      * 活体检测结果
@@ -133,7 +112,6 @@ public class ExamRecordDataDomain implements JsonSerializable {
      * 百度人脸活体检测通过率
      */
     private Double baiduFaceLivenessSuccessPercent;
-
     /**
      * 人脸五官坐标比对值
      */
@@ -369,5 +347,4 @@ public class ExamRecordDataDomain implements JsonSerializable {
     public void setCreationTime(Date creationTime) {
         this.creationTime = creationTime;
     }
-
 }

+ 63 - 64
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/bean/IllegallyTypeDomain.java

@@ -6,92 +6,91 @@ import java.util.Date;
 
 public class IllegallyTypeDomain implements JsonSerializable {
 
-    private static final long serialVersionUID = 239263456816448160L;
+	private static final long serialVersionUID = 239263456816448160L;
 
-    private Long id;
+	private Long id;
 
-    private String code;
+	private String code;
 
-    private String name;
+	private String name;
 
-    private Long rootOrgId;
+	private Long rootOrgId;
 
-    private Boolean enable;
+	private Boolean enable;
 
-    /**
-     * 排序号
-     */
-    private Integer sortNo;
+	/**
+	 * 排序号
+	 */
+	private Integer sortNo;
 
-    /**
-     * 数据分类
-     */
-    private String dataCategory;
+	/**
+	 * 数据分类
+	 */
+	private String dataCategory;
 
-    private Date updateTime;
+	private Date updateTime;
 
-    public Long getId() {
-        return id;
-    }
+	public Long getId() {
+		return id;
+	}
 
-    public void setId(Long id) {
-        this.id = id;
-    }
+	public void setId(Long id) {
+		this.id = id;
+	}
 
-    public String getCode() {
-        return code;
-    }
+	public String getCode() {
+		return code;
+	}
 
-    public void setCode(String code) {
-        this.code = code;
-    }
+	public void setCode(String code) {
+		this.code = code;
+	}
 
-    public String getName() {
-        return name;
-    }
+	public String getName() {
+		return name;
+	}
 
-    public void setName(String name) {
-        this.name = name;
-    }
+	public void setName(String name) {
+		this.name = name;
+	}
 
-    public Long getRootOrgId() {
-        return rootOrgId;
-    }
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
 
-    public void setRootOrgId(Long rootOrgId) {
-        this.rootOrgId = rootOrgId;
-    }
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
 
-    public Boolean getEnable() {
-        return enable;
-    }
+	public Boolean getEnable() {
+		return enable;
+	}
 
-    public void setEnable(Boolean enable) {
-        this.enable = enable;
-    }
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
 
-    public Integer getSortNo() {
-        return sortNo;
-    }
+	public Integer getSortNo() {
+		return sortNo;
+	}
 
-    public void setSortNo(Integer sortNo) {
-        this.sortNo = sortNo;
-    }
+	public void setSortNo(Integer sortNo) {
+		this.sortNo = sortNo;
+	}
 
-    public String getDataCategory() {
-        return dataCategory;
-    }
+	public String getDataCategory() {
+		return dataCategory;
+	}
 
-    public void setDataCategory(String dataCategory) {
-        this.dataCategory = dataCategory;
-    }
+	public void setDataCategory(String dataCategory) {
+		this.dataCategory = dataCategory;
+	}
 
-    public Date getUpdateTime() {
-        return updateTime;
-    }
-
-    public void setUpdateTime(Date updateTime) {
-        this.updateTime = updateTime;
-    }
+	public Date getUpdateTime() {
+		return updateTime;
+	}
 
+	public void setUpdateTime(Date updateTime) {
+		this.updateTime = updateTime;
+	}
 }

+ 35 - 15
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamAuditController.java

@@ -7,6 +7,23 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.JsonMapper;
@@ -21,7 +38,12 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.enums.AuditStatus;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.SelectType;
 import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamAuditService;
-import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.RedoAuditInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
 import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
 import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
@@ -29,19 +51,10 @@ import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
 
 /**
  * 考试记录审核相关接口
@@ -66,12 +79,15 @@ public class ExamAuditController extends ControllerSupport {
     @Autowired
     private AsyncExportService asyncExportService;
 
+    
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/list")
     @ApiOperation(value = "查询“监考已审”列表(分页)")
     public Page<ExamAuditInfo> getExamAuditList(@RequestBody ExamAuditQuery query) {
         User user = getAccessUser();
         query.setRootOrgId(user.getRootOrgId());
-        Page<ExamAuditInfo> examAuditList = examAuditService.getExamAuditList(query);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamAuditInfo> examAuditList = examAuditService.getExamAuditList(uds,query);
 
         examAuditList.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(user.getRootOrgId(), p.getIdentityNumber()));
@@ -79,6 +95,7 @@ public class ExamAuditController extends ControllerSupport {
         return examAuditList;
     }
 
+
     @GetMapping("export/async")
     @ApiOperation(value = "导出“监考已审”(异步)")
     public void exportExamAuditList(@RequestParam String query) {
@@ -88,13 +105,15 @@ public class ExamAuditController extends ControllerSupport {
         ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "监考已审-导出", "导出条件:" + query));
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/discipline/list")
     @ApiOperation(value = "查询“违纪名单”列表(分页)")
     public Page<ExamAuditInfo> getExamAuditUnPassList(@RequestBody ExamAuditQuery query) {
         query.setStatus(AuditStatus.UN_PASS.name());
         User user = getAccessUser();
         query.setRootOrgId(user.getRootOrgId());
-        Page<ExamAuditInfo> examAuditList = examAuditService.getExamAuditList(query);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamAuditInfo> examAuditList = examAuditService.getExamAuditList(uds,query);
 
         examAuditList.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(user.getRootOrgId(), p.getIdentityNumber()));
@@ -102,7 +121,7 @@ public class ExamAuditController extends ControllerSupport {
         return examAuditList;
     }
 
-    @Naked
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @GetMapping("/discipline/list/export")
     @ApiOperation(value = "导出“违纪名单”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamAuditUnPassList(@RequestParam String query, HttpServletResponse response) throws Exception {
@@ -112,7 +131,8 @@ public class ExamAuditController extends ControllerSupport {
 
         newQuery.setStatus(AuditStatus.UN_PASS.name());
         newQuery.setSelectType(SelectType.EXPORT);
-        Page<ExamAuditInfo> page = examAuditService.getExamAuditList(newQuery);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamAuditInfo> page = examAuditService.getExamAuditList(uds,newQuery);
         List<ExamAuditExcel> list = ExamAuditEntityConvert.ofExcel(page);
         ExportUtils.exportEXCEL("违纪名单列表", ExamAuditExcel.class, list, response);
     }

+ 44 - 28
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordController.java

@@ -7,9 +7,9 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
-import cn.com.qmth.examcloud.commons.util.JsonMapper;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
@@ -18,7 +18,9 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordFileAnswerEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamCaptureService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamProcessRecordService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
@@ -28,8 +30,8 @@ import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
 import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang3.StringUtils;
@@ -68,12 +70,18 @@ public class ExamRecordController extends ControllerSupport {
 
     @Autowired
     private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
+    
+    @Autowired
+    private ExamProcessRecordService examProcessRecordService;
+    
+    
 
-
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/waiting/audit/list")
     @ApiOperation(value = "查询“监考待审”列表(分页)")
     public Page<ExamRecordInfo> getExamRecordWaitingAuditList(@RequestBody ExamRecordQuery query) {
-        Page<ExamRecordInfo> examRecordWaitingAuditList = examRecordService.getExamRecordWaitingAuditList(query);
+    	UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamRecordInfo> examRecordWaitingAuditList = examRecordService.getExamRecordWaitingAuditList(uds,query);
         examRecordWaitingAuditList.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
@@ -85,29 +93,36 @@ public class ExamRecordController extends ControllerSupport {
                 if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
                     examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
                     examRecordInfo.setHasVirtual(StringUtils.isNotBlank(examRecordInfo.getVirtualCameraNames()));
+                    examRecordInfo.setIp(examProcessRecordService.getIps(examRecordInfo.getDataId()));
                 }
             }
         }
         return examRecordWaitingAuditList;
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/waiting/audit/next")
     @ApiOperation(value = "查询“监考待审”下一条记录")
     public Long getExamRecordWaitingAuditNextId(@RequestBody ExamRecordQuery query,
                                                 @RequestParam Long examRecordDataId, String next) {
-        return examRecordService.getExamRecordWaitingAuditNextId(query, examRecordDataId, next);
+    	UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        return examRecordService.getExamRecordWaitingAuditNextId(uds,query, examRecordDataId, next);
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/detail/check")
     @ApiOperation(value = "查询“考试明细” 是否存在待审数据")
     public Long existsWarnExamRecordDetail(@RequestBody ExamRecordQuery query) {
-        return examRecordService.existsWarnExamRecordDetail(query);
+    	UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        return examRecordService.existsWarnExamRecordDetail(uds,query);
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/detail/list")
     @ApiOperation(value = "查询“考试明细”列表(分页)")
     public Page<ExamRecordInfo> getExamRecordDetailList(@RequestBody ExamRecordQuery query) {
-        Page<ExamRecordInfo> examRecordInfoPage = examRecordService.getExamRecordDetailListForPage(query);
+    	UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamRecordInfo> examRecordInfoPage = examRecordService.getExamRecordDetailListForPage(uds,query);
         List<ExamRecordInfo> examRecordInfoList = examRecordInfoPage.getContent();
         if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
             String examType = examRecordInfoList.get(0).getExamType();
@@ -126,6 +141,7 @@ public class ExamRecordController extends ControllerSupport {
                 }
                 if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
                     examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
+                    examRecordInfo.setIp(examProcessRecordService.getIps(examRecordInfo.getDataId()));
                 }
             }
         }
@@ -150,27 +166,27 @@ public class ExamRecordController extends ControllerSupport {
         return resultList;
     }
 
-    @Naked
-    @GetMapping("/detail/list/export")
-    @ApiOperation(value = "导出“考试明细”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
-    public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamRecordQuery newQuery = new JsonMapper().parseJson(query, ExamRecordQuery.class);
-        Check.isNull(newQuery, "请求参数不能为空!");
-        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
-
-        List<ExamRecordInfo> examRecordInfoList = examRecordService.getExamRecordDetailList(newQuery);
-        if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
-            String examType = examRecordInfoList.get(0).getExamType();
-
-            for (ExamRecordInfo examRecordInfo : examRecordInfoList) {
-                if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
-                    examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
-                }
-            }
-        }
-
-        ExportUtils.exportEXCEL("考试明细列表", ExamRecordInfo.class, examRecordInfoList, response);
-    }
+//    @Naked
+//    @GetMapping("/detail/list/export")
+//    @ApiOperation(value = "导出“考试明细”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+//    public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
+//        ExamRecordQuery newQuery = new JsonMapper().parseJson(query, ExamRecordQuery.class);
+//        Check.isNull(newQuery, "请求参数不能为空!");
+//        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+//
+//        List<ExamRecordInfo> examRecordInfoList = examRecordService.getExamRecordDetailList(newQuery);
+//        if (examRecordInfoList != null && examRecordInfoList.size() > 0) {
+//            String examType = examRecordInfoList.get(0).getExamType();
+//
+//            for (ExamRecordInfo examRecordInfo : examRecordInfoList) {
+//                if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
+//                    examRecordInfo.setVirtualCameraNames(examCaptureService.getVirtualCameraNames(examRecordInfo.getDataId()));
+//                }
+//            }
+//        }
+//
+//        ExportUtils.exportEXCEL("考试明细列表", ExamRecordInfo.class, examRecordInfoList, response);
+//    }
 
     @GetMapping("/detail/list/export/async")
     @ApiOperation(value = "导出“考试明细”列表(异步)", notes = "参数示例:query={\"rootOrgId\":0,\"examId\":1}")

+ 0 - 1
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamRecordPaperStructController.java

@@ -34,7 +34,6 @@ public class ExamRecordPaperStructController extends ControllerSupport {
     @ApiOperation(value = "获取考试记录试卷结构")
     @GetMapping("/getExamRecordPaperStruct")
     public ExamRecordPaperStructEntity getExamRecordPaperStruct(@RequestParam Long examRecordDataId, @RequestParam(required = false) String fromCache) {
-        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
         if (fromCache != null) {
             examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
             if (examRecordDataId == null) {

+ 17 - 146
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamScoreController.java

@@ -1,42 +1,25 @@
-/*
- * *************************************************
- * Copyright (c) 2018 QMTH. All Rights Reserved.
- * Created by Deason on 2018-08-31 09:28:03.
- * *************************************************
- */
-
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
-import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.util.JsonMapper;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.excel.ExportUtils;
 import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
 import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
 import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
-import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
-import cn.com.qmth.examcloud.support.enums.ExamRecordStatus;
-import cn.com.qmth.examcloud.support.examing.ExamBoss;
-import cn.com.qmth.examcloud.support.examing.ExamRecordData;
-import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
-import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.web.bind.annotation.*;
 
-import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 
 /**
@@ -56,13 +39,12 @@ public class ExamScoreController extends ControllerSupport {
     @Autowired
     private ExamScoreService examScoreService;
 
-    @Autowired
-    private RedisClient redisClient;
-
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/statistic/list")
     @ApiOperation(value = "查询“成绩统计”列表(分页)")
     public Page<ExamScoreInfo> getExamAuditList(@RequestBody ExamScoreQuery query) {
-        Page<ExamScoreInfo> examScoreList = examScoreService.getExamScoreList(query);
+    	UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamScoreInfo> examScoreList = examScoreService.getExamScoreList(uds,query);
 
         examScoreList.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(getRootOrgId(), p.getIdentityNumber()));
@@ -70,16 +52,16 @@ public class ExamScoreController extends ControllerSupport {
         return examScoreList;
     }
 
-    @Naked
-    @GetMapping("/statistic/list/export")
-    @ApiOperation(value = "导出“成绩统计”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
-    public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamScoreQuery newQuery = new JsonMapper().parseJson(query, ExamScoreQuery.class);
-        Check.isNull(newQuery, "请求参数不能为空!");
-        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
-        List<ExamScoreInfo> examScoreList = examScoreService.exportExamScoreList(newQuery);
-        ExportUtils.exportEXCEL("成绩统计列表", ExamScoreInfo.class, examScoreList, response);
-    }
+//    @Naked
+//    @GetMapping("/statistic/list/export")
+//    @ApiOperation(value = "导出“成绩统计”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
+//    public void exportExamRecordDetailList(@RequestParam String query, HttpServletResponse response) throws Exception {
+//        ExamScoreQuery newQuery = new JsonMapper().parseJson(query, ExamScoreQuery.class);
+//        Check.isNull(newQuery, "请求参数不能为空!");
+//        Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
+//        List<ExamScoreInfo> examScoreList = examScoreService.exportExamScoreList(newQuery);
+//        ExportUtils.exportEXCEL("成绩统计列表", ExamScoreInfo.class, examScoreList, response);
+//    }
 
     @GetMapping("/statistic/list/export/async")
     @ApiOperation(value = "导出“成绩统计”列表(异步)",
@@ -94,118 +76,7 @@ public class ExamScoreController extends ControllerSupport {
     @ApiOperation(value = "根据examStudentId获取客观分信息")
     @GetMapping("/queryObjectiveScoreList")
     public List<ObjectiveScoreInfo> queryObjectiveScoreList(@RequestParam Long examStudentId) {
-        Check.isNull(examStudentId, "examStudentId 不能为空");
-
-        List<ObjectiveScoreInfo> resultList = examScoreService.queryObjectiveScoreList(examStudentId);
-
-
-        //如果有未处理完成的考试记录,需要将未处理的考试记录数据添加到列表中
-        String examBossKey = RedisKeyHelper.getBuilder().examBossKey(examStudentId);
-        ExamBoss examBoss = redisClient.get(examBossKey, ExamBoss.class);
-        if (null != examBoss) {
-            //未完全同步的考试记录id
-            List<Long> unSyncedExamRecordDataIds = examBoss.getExamRecordDataIds();
-            if (null != unSyncedExamRecordDataIds && !unSyncedExamRecordDataIds.isEmpty()) {
-                //正序排列
-                unSyncedExamRecordDataIds.sort(Long::compareTo);
-
-                //已考次数
-                Integer examUsedNum = 0;
-
-                for (Long examRecordDataId : unSyncedExamRecordDataIds) {
-                    ExamRecordData examRecordData =
-                            redisClient.get(RedisKeyHelper.getBuilder().examRecordDataKey(examRecordDataId),
-                                    ExamRecordData.class);
-                    if (null == examRecordData) {
-                        throw new StatusException("100001", "考试记录的缓存数据有误");
-                    }
-
-                    //考试中的数据不展示
-                    if (ExamRecordStatus.EXAM_ING == examRecordData.getExamRecordStatus()) {
-                        continue;
-                    }
-
-                    if (!resultList.isEmpty()) {
-                        examUsedNum = resultList.get(resultList.size() - 1).getExamOrder();
-                    }
-
-                    ObjectiveScoreInfo cachedObjectiveScoreInfo = getCachedObjectiveScoreInfo(examRecordData);
-                    cachedObjectiveScoreInfo.setExamOrder(
-                            getExamOrder(examRecordData.getExamId(), examRecordData.getStudentId(), examUsedNum));
-
-                    resultList.add(cachedObjectiveScoreInfo);
-                }
-            }
-        }
-
-        return resultList;
-    }
-
-    private ObjectiveScoreInfo getCachedObjectiveScoreInfo(final ExamRecordData examRecordData) {
-
-
-        ObjectiveScoreInfo objectiveScoreInfo = new ObjectiveScoreInfo();
-        objectiveScoreInfo.setExamRecordDataId(examRecordData.getId());
-        objectiveScoreInfo.setStartTime(examRecordData.getStartTime());
-        objectiveScoreInfo.setEndTime(examRecordData.getEndTime());
-
-        //如果考试没有结束,则只能返回部分数据
-        if (!isExamRecordEnded(examRecordData)) {
-            objectiveScoreInfo.setIsExamEnded(false);
-            return objectiveScoreInfo;
-        } else {
-            objectiveScoreInfo.setIsExamEnded(true);
-        }
-
-        if (examRecordData.getIsIllegality() == null || !examRecordData.getIsIllegality()) {
-            if ((null != examRecordData.getIsWarn() && !examRecordData.getIsWarn())
-                    || (null != examRecordData.getIsWarn() && examRecordData.getIsWarn()
-                    && null != examRecordData.getIsAudit() && examRecordData.getIsAudit())) {
-                objectiveScoreInfo.setIsAuditing(true);
-
-                //缓存中的分数是存储在临时考试记录表中的,所以需要从考试记录缓存中取
-                objectiveScoreInfo.setObjectiveScore(examRecordData.getObjectiveScore());
-            } else {
-                objectiveScoreInfo.setIsAuditing(false);
-            }
-
-            objectiveScoreInfo.setIsIllegality(false);
-        } else {
-            objectiveScoreInfo.setIsIllegality(true);
-        }
-
-        return objectiveScoreInfo;
-    }
-
-    private boolean isExamRecordEnded(ExamRecordData examRecordData) {
-        //如果考试记录状态为已处理,则直接返回true.
-        if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_END ||
-                examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_OVERDUE ||
-                examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_INVALID) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * 计算考试次数
-     *
-     * @param examId      考试id
-     * @param studentId   学生id
-     * @param usedExamNum 已考次数
-     * @return
-     */
-    private Integer getExamOrder(Long examId, Long studentId, Integer usedExamNum) {
-        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getDefaultCachedExam(examId);
-        Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
-
-        //超过可考次数,始终为可考次数+1
-        if (usedExamNum > canExamTimes) {
-            return canExamTimes;
-        }
-
-        return usedExamNum;
-
+        return examScoreService.queryObjectiveScoreList(examStudentId);
     }
 
 }

+ 68 - 23
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/ExamStudentController.java

@@ -7,7 +7,28 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.commons.util.JsonMapper;
 import cn.com.qmth.examcloud.core.basic.api.bean.CourseBean;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
@@ -16,25 +37,27 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.IllegallyTypeRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.IllegallyTypeEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
-import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.CourseCompleteProgressExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.CourseProgressInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentFinishedStatistic;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentOrgStatistic;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentUnFinishedExcel;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.OrgCompleteProgressExcel;
 import cn.com.qmth.examcloud.core.oe.admin.service.util.AsyncExportConcurrentUtil;
 import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
 import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.web.bind.annotation.*;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * 考生信息接口
@@ -83,12 +106,14 @@ public class ExamStudentController extends ControllerSupport {
         timer.schedule(new CleanCourseBeanCacheTask(), delay, period);
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/examScheduling/list")
     @ApiOperation(value = "查询“考试进度详情”列表(分页)")
     public Page<ExamStudentInfo> getExamScheduling(@RequestBody ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "考试批次不能为空!");
-        Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(query);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(uds,query);
 
         examStudentListPage.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
@@ -96,13 +121,15 @@ public class ExamStudentController extends ControllerSupport {
         return examStudentListPage;
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @GetMapping("/examScheduling/list/export")
     @ApiOperation(value = "导出“考试进度详情”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamStudentList(@RequestParam String query, HttpServletResponse response) throws Exception {
         ExamStudentQuery newQuery = new JsonMapper().parseJson(query, ExamStudentQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
-        List<ExamStudentInfo> examStudentInfoList = examStudentService.getExamStudentInfoList(newQuery);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        List<ExamStudentInfo> examStudentInfoList = examStudentService.getExamStudentInfoList(uds,newQuery);
         List<ExamStudentExcel> list = ExamStudentEntityConvert.ofExcel(examStudentInfoList);
         ExportUtils.exportEXCEL("考试详情列表", ExamStudentExcel.class, list, response);
         //        ExcelExportUtil.exportExcel("考试详情列表", ExamStudentExcel.class, list, response);
@@ -117,38 +144,43 @@ public class ExamStudentController extends ControllerSupport {
         ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-导出", "导出条件:" + query));
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/unfinished/list")
     @ApiOperation(value = "查询“缺考登记”列表(分页)")
     public Page<ExamStudentInfo> getExamStudentUnFinishedList(@RequestBody ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "考试批次不能为空!");
         query.setFinished(0);
-        Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(query);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamStudentInfo> examStudentListPage = examStudentService.getExamStudentListPage(uds,query);
         examStudentListPage.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
         return examStudentListPage;
     }
 
-    @Naked
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @GetMapping("/unfinished/list/export")
     @ApiOperation(value = "导出“缺考登记”列表(Excel)", notes = "参数示例:query={\"pageNo\":1,\"pageSize\":10,\"examId\":123, ...}")
     public void exportExamStudentUnFinishedList(@RequestParam String query, HttpServletResponse response) throws Exception {
-        ExamStudentQuery newQuery = new JsonMapper().parseJson(query, ExamStudentQuery.class);
+    	ExamStudentQuery newQuery = new JsonMapper().parseJson(query, ExamStudentQuery.class);
         Check.isNull(newQuery, "请求参数不能为空!");
         Check.isNull(newQuery.getExamId(), "请先选择考试批次!");
         newQuery.setFinished(0);//未完成
-        List<ExamStudentInfo> examStudentInfos = examStudentService.getExamStudentInfoList(newQuery);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        List<ExamStudentInfo> examStudentInfos = examStudentService.getExamStudentInfoList(uds,newQuery);
         List<ExamStudentUnFinishedExcel> list = ExamStudentEntityConvert.ofUnFinishedExcel(examStudentInfos);
         ExportUtils.exportEXCEL("缺考登记列表", ExamStudentUnFinishedExcel.class, list, response);
     }
 
+    @DataRule(type = {DataRuleType.COURSE,DataRuleType.ORG})
     @PostMapping("/reexamine/list")
     @ApiOperation(value = "查询重考考生列表(分页)")
     public Page<ExamStudentInfo> getReExamineStudentList(@RequestBody ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "考试批次不能为空!");
-        Page<ExamStudentInfo> reExamineStudentList = examStudentService.getReExamineStudentList(query);
+        UserDataRules uds=new UserDataRules(getUserDataRule(DataRuleType.ORG), getUserDataRule(DataRuleType.COURSE));
+        Page<ExamStudentInfo> reExamineStudentList = examStudentService.getReExamineStudentList(uds,query);
         reExamineStudentList.getContent().forEach(p -> {
             p.setIdentityNumber(IdentityNumberHelper.conceal(p.getRootOrgId(), p.getIdentityNumber()));
         });
@@ -189,6 +221,7 @@ public class ExamStudentController extends ControllerSupport {
         return examStudentService.getExamStudentStatisticByFinished(examId, examStageId);
     }
 
+    @DataRule(type = {DataRuleType.ORG})
     @PostMapping("/statistic/by/org")
     @ApiOperation(value = "考试概况-按学习中心统计")
     public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(@RequestParam Long examId,
@@ -197,9 +230,10 @@ public class ExamStudentController extends ControllerSupport {
         if (examId == null) {
             return null;
         }
-        return examStudentService.getExamStudentStatisticByOrg(examId, examStageId, orgId);
+        return examStudentService.getExamStudentStatisticByOrg(getUserDataRule(DataRuleType.ORG),examId, examStageId, orgId);
     }
 
+    @DataRule(type = {DataRuleType.ORG})
     @GetMapping("/statistic/by/org/export")
     @ApiOperation(value = "导出“考试概况-按学习中心统计”列表(Excel)")
     public void exportExamStudentStatisticByOrg(@RequestParam Long examId,
@@ -208,7 +242,7 @@ public class ExamStudentController extends ControllerSupport {
                                                 HttpServletResponse response) throws Exception {
         Check.isNull(examId, "请选择考试批次!");
         List<ExamStudentOrgStatistic> examStudentStatisticByOrgList =
-                examStudentService.getExamStudentStatisticByOrg(examId, examStageId, orgId);
+                examStudentService.getExamStudentStatisticByOrg(getUserDataRule(DataRuleType.ORG),examId, examStageId, orgId);
         List<OrgCompleteProgressExcel> resultList =
                 ExamStudentEntityConvert.copyFromExamStudentOrgStatistic(examStudentStatisticByOrgList);
         ExportUtils.exportEXCEL("学习中心完成进度", OrgCompleteProgressExcel.class, resultList, response);
@@ -221,6 +255,7 @@ public class ExamStudentController extends ControllerSupport {
      * @param orgId
      * @return
      */
+    @DataRule(type = DataRuleType.COURSE)
     @GetMapping("/findCoursesByExamIdAndOrgId")
     @ApiOperation(value = "查询课程")
     public List<CourseCacheBean> findCoursesByExamIdAndOrgId(@RequestParam Long examId,
@@ -230,10 +265,16 @@ public class ExamStudentController extends ControllerSupport {
             return null;
         }
         List<Long> courseIdList = examStudentService.findCoursesFromExamStudent(examId, examStageId, orgId);
+        UserDataRule ud=getUserDataRule(DataRuleType.COURSE);
         List<CourseCacheBean> courseBeanList = new ArrayList<CourseCacheBean>();
+        if(ud.assertEmptyQueryResult()) {
+        	return courseBeanList;
+        }
         for (Long courseId : courseIdList) {
-            CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
-            courseBeanList.add(courseBean);
+        	if(!ud.assertNeedQueryRefIds()||ud.getRefIds().contains(courseId)) {
+	            CourseCacheBean courseBean = ExamCacheTransferHelper.getCachedCourse(courseId);
+	            courseBeanList.add(courseBean);
+        	}
         }
         return courseBeanList;
     }
@@ -245,6 +286,7 @@ public class ExamStudentController extends ControllerSupport {
      * @param courseId
      * @return
      */
+    @DataRule(type = DataRuleType.COURSE)
     @GetMapping("/courseProgress/list")
     @ApiOperation(value = "课程完成进度")
     public List<CourseProgressInfo> queryCourseProgressInfos(@RequestParam Long examId,
@@ -254,8 +296,9 @@ public class ExamStudentController extends ControllerSupport {
         if (examId == null) {
             return null;
         }
+        UserDataRule ud=getUserDataRule(DataRuleType.COURSE);
         List<CourseProgressInfo> courseProgressInfoList =
-                examStudentService.queryCourseProgressInfos(examId, examStageId, courseId, orderColumn);
+                examStudentService.queryCourseProgressInfos(ud,examId, examStageId, courseId, orderColumn);
         if (courseProgressInfoList != null && courseProgressInfoList.size() > 0) {
             for (CourseProgressInfo courseProgressInfo : courseProgressInfoList) {
                 long key = courseProgressInfo.getCourseId();
@@ -267,6 +310,7 @@ public class ExamStudentController extends ControllerSupport {
         return courseProgressInfoList;
     }
 
+    @DataRule(type = DataRuleType.COURSE)
     @GetMapping("/courseProgress/list/export")
     @ApiOperation(value = "导出“课程完成进度”列表(Excel)")
     public void exportCourseProgressInfos(@RequestParam Long examId,
@@ -275,8 +319,9 @@ public class ExamStudentController extends ControllerSupport {
                                           @RequestParam(required = false) String orderColumn,
                                           HttpServletResponse response) throws Exception {
         Check.isNull(examId, "请选择考试批次!");
+        UserDataRule ud=getUserDataRule(DataRuleType.COURSE);
         List<CourseProgressInfo> courseProgressInfoList =
-                examStudentService.queryCourseProgressInfos(examId, examStageId, courseId, orderColumn);
+                examStudentService.queryCourseProgressInfos(ud,examId, examStageId, courseId, orderColumn);
         if (courseProgressInfoList != null && courseProgressInfoList.size() > 0) {
             for (CourseProgressInfo courseProgressInfo : courseProgressInfoList) {
                 long key = courseProgressInfo.getCourseId();

+ 43 - 186
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/OfflineExamController.java

@@ -1,34 +1,25 @@
 package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
-import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.commons.util.MD5;
+import cn.com.qmth.examcloud.commons.util.FileUtil;
+import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
-import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
-import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
 import cn.com.qmth.examcloud.core.oe.admin.service.OfflineExamService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.OfflineExamCourseInfo;
-import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
-import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
-import cn.com.qmth.examcloud.support.enums.ExamProperties;
-import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.uploadfile.FileInfo;
 import cn.com.qmth.examcloud.web.config.SystemProperties;
-import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.*;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -50,22 +41,7 @@ public class OfflineExamController extends ControllerSupport {
     private OfflineExamService offlineExamService;
 
     @Autowired
-    private ExamRecordDataRepo examRecordDataRepo;
-
-    @Autowired
-    private ExamStudentService examStudentService;
-
-    @Autowired
-    SystemProperties systemConfig;
-
-    /**
-     * 答案文件最大限制
-     * 单位:M
-     */
-    private static int answerMaxsize = 30;
-
-    public static final String TEMP_FILE_EXP = "offlineExam/";
-
+    private SystemProperties systemConfig;
 
     /**
      * 获取离线考试列表
@@ -76,8 +52,7 @@ public class OfflineExamController extends ControllerSupport {
     @GetMapping("/getOfflineCourse")
     public List<OfflineExamCourseInfo> getOfflineCourse() {
         User user = getAccessUser();
-        List<OfflineExamCourseInfo> offlineExamCourseInfos = offlineExamService.getOfflineCourse(user.getUserId());
-        return offlineExamCourseInfos;
+        return offlineExamService.getOfflineCourse(user.getUserId());
     }
 
     /**
@@ -88,13 +63,6 @@ public class OfflineExamController extends ControllerSupport {
     @ApiOperation(value = "离线考试:开始考试")
     @GetMapping("/startOfflineExam")
     public void startOfflineExam(@RequestParam long examStudentId) {
-        Check.isNull(examStudentId, "examStudentId不能为空");
-
-        boolean isEnable = examStudentService.isEnableExamStudent(examStudentId);
-        if (!isEnable) {
-            throw new StatusException("000500", "当前考生已禁用!");
-        }
-
         offlineExamService.startOfflineExam(examStudentId);
     }
 
@@ -103,52 +71,19 @@ public class OfflineExamController extends ControllerSupport {
      */
     @ApiOperation(value = "离线考试:交卷")
     @PostMapping("/submitPaper")
-    public void submitPaper(@RequestParam(value = "file") MultipartFile file,
-                            @RequestParam long examRecordDataId) throws Exception {
-        Check.isNull(file, "file不能为空");
-        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
-
-        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-        if (examRecordData.getExamType() != ExamType.OFFLINE) {
-            throw new StatusException("OfflineExamController-submitPaper-002", "非离线考试");
-        }
-
-        boolean isEnable = examStudentService.isEnableExamStudent(examRecordData.getExamStudentId());
-        if (!isEnable) {
-            throw new StatusException("000500", "当前考生已禁用!");
-        }
-
-        String fileName = file.getOriginalFilename();
-        int index = fileName.lastIndexOf(".");
-        String fileSuffix = fileName.substring(index + 1, fileName.length()).toUpperCase();
-
-        if (!"PDF".equals(fileSuffix) && !"ZIP".equals(fileSuffix)
-                && !"JPG".equals(fileSuffix) && !"JPEG".equals(fileSuffix) && !"PNG".equals(fileSuffix)) {
-            throw new StatusException("OfflineExamController-submitPaper-001", "文件格式不正确");
-        }
-
-        String offlineUploadFileType = ExamCacheTransferHelper.getCachedExamProperty(examRecordData.getExamId(),
-                examRecordData.getStudentId(),
-                ExamProperties.OFFLINE_UPLOAD_FILE_TYPE.name()).getValue();
-        if (StringUtils.isBlank(offlineUploadFileType) || "[]".equals(offlineUploadFileType)) {
-            throw new StatusException("OfflineExamController-submitPaper-003", "当前考试设置不允许上传附件");
-        }
-        if (offlineUploadFileType.indexOf(fileSuffix) < 0) {
-            throw new StatusException("OfflineExamController-submitPaper-004", "当前考试允许上传文件格式为:" + offlineUploadFileType);
-        }
-        //判断文件大小
-        long fileSize = file.getSize();
-
-        if (fileSize > answerMaxsize * 1048576) {
-            throw new StatusException("OfflineExamController-submitPaper-005", "文件大小不能超过" + answerMaxsize + "M");
-        }
-
-        File f = getUploadFile(file);
+    public void submitPaper(@RequestParam long examRecordDataId,
+                            @RequestPart(value = "file", required = false) MultipartFile file) throws Exception {
+        Check.isNull(file, "上传文件不能为空");
+        User user = getAccessUser();
 
-        offlineExamService.submitPaper(examRecordDataId, f, fileSuffix);
+        String fileSuffix = FileUtil.getFileSuffix(file.getOriginalFilename());
+        FileInfo fileInfo = new FileInfo();
+        fileInfo.setOriginalFileName(file.getOriginalFilename());
+        fileInfo.setFileBytes(file.getBytes());
+        fileInfo.setFileSize(file.getSize());
+        fileInfo.setFileSuffix(fileSuffix);
 
-        User user = getAccessUser();
-        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-上传作答", "考试记录ID:" + examRecordDataId + " 考生ID:" + examRecordData.getExamStudentId()));
+        offlineExamService.submitPaper(examRecordDataId, fileInfo, user.getUserId());
     }
 
     /**
@@ -161,122 +96,44 @@ public class OfflineExamController extends ControllerSupport {
      */
     @ApiOperation(value = "离线考试:多文件交卷")
     @PostMapping("/batchSubmitPaper")
-    public void batchSubmitPaper(@ApiParam(value = "文件数组") @RequestParam MultipartFile[] fileArray,
-                                 @ApiParam(value = "考试记录id") @RequestParam long examRecordDataId,
-                                 @ApiParam(value = "文件类型:ZIP/PDF/IMAGE") @RequestParam String fileType,
-                                 @ApiParam(value = "md5加密的文件摘要") @RequestParam String[] fileMd5Array) throws Exception {
-        Check.isNull(fileArray, "files不能为空");
-        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
-        if (fileArray.length != fileMd5Array.length) {
-            throw new StatusException("200001", "文件数量和文件摘要信息不匹配");
-        }
-
-        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-        if (examRecordData.getExamType() != ExamType.OFFLINE) {
-            throw new StatusException("100003", "非离线考试");
-        }
-
-        boolean isEnable = examStudentService.isEnableExamStudent(examRecordData.getExamStudentId());
-        if (!isEnable) {
-            throw new StatusException("000500", "当前考生已禁用!");
-        }
-
-        String offlineUploadFileType = ExamCacheTransferHelper.getCachedExamProperty(examRecordData.getExamId(),
-                examRecordData.getStudentId(),
-                ExamProperties.OFFLINE_UPLOAD_FILE_TYPE.name()).getValue();
-        if (StringUtils.isBlank(offlineUploadFileType) || "[]".equals(offlineUploadFileType)) {
-            throw new StatusException("OfflineExamController-submitPaper-003", "当前考试设置不允许上传附件");
-        }
+    public void batchSubmitPaper(@ApiParam(value = "考试记录id") @RequestParam Long examRecordDataId,
+                                 @ApiParam(value = "文件类型:ZIP、PDF、IMAGE(单选、三选一)") @RequestParam String fileType,
+                                 @ApiParam(value = "文件MD5信息") @RequestParam String[] fileMd5Array,
+                                 @ApiParam(value = "文件数组") @RequestPart(required = false) MultipartFile[] fileArray) throws Exception {
+        Check.isNull(fileArray, "上传文件不能为空");
+        User user = getAccessUser();
 
-        String upperFileType = fileType.toUpperCase();
-        if (("PDF".equals(upperFileType) || "ZIP".equals(upperFileType)) && fileArray.length > 1) {
-            StringBuilder sb = new StringBuilder();
-            for (int i = 0; i < fileArray.length; i++) {
-                MultipartFile file = fileArray[i];
-                String fileName = file.getOriginalFilename();
-                sb.append(fileName).append(",");
-            }
-            sb.deleteCharAt(sb.length() - 1);
-            log.error("batchSubmitPaper-duplicate:" + examRecordDataId + ":" + sb);
-            throw new StatusException("000500", "上传" + upperFileType + "类型文件时只能传一个文件");
+        if (fileArray.length != fileMd5Array.length) {
+            throw new StatusException("400401", "文件数量和文件MD5数量不匹配");
         }
 
-        List<File> resultFiles = new ArrayList<>();
+        List<FileInfo> fileInfoList = new ArrayList<>();
         for (int i = 0; i < fileArray.length; i++) {
             MultipartFile file = fileArray[i];
-            String fileName = file.getOriginalFilename();
-            int index = fileName.lastIndexOf(".");
-            String fileSuffix = fileName.substring(index + 1, fileName.length()).toUpperCase();
-
-            if (!"PDF".equals(upperFileType) && !"ZIP".equals(upperFileType)
-                    && !"IMAGE".equals(upperFileType)) {
-                throw new StatusException("100001", "文件类型不正确");
-            }
+            log.info("OfflineExam file {} - {}", i + 1, file.getOriginalFilename());
 
-            if (!"PDF".equals(fileSuffix) && !"ZIP".equals(fileSuffix)
-                    && !"JPG".equals(fileSuffix) && !"JPEG".equals(fileSuffix) && !"PNG".equals(fileSuffix)) {
-                throw new StatusException("100002", "文件格式不正确");
+            if (file.getSize() > Constants.ANSWER_FILE_MAX_SIZE * Constants.M_SIZE) {
+                throw new StatusException("400402", "文件大小不能超过" + Constants.ANSWER_FILE_MAX_SIZE + "M");
             }
 
-
-            //图片类型特殊处理
-            if (fileSuffix.equals("JPG") || fileSuffix.equals("JPEG") || fileSuffix.equals("PNG")) {
-                if (offlineUploadFileType.indexOf("IMAGE") < 0) {
-                    throw new StatusException("100004", "当前考试允许上传文件格式为:" + offlineUploadFileType);
-                }
-            } else {
-                if (offlineUploadFileType.indexOf(fileSuffix) < 0) {
-                    throw new StatusException("OfflineExamController-submitPaper-004", "当前考试允许上传文件格式为:" + offlineUploadFileType);
-                }
-            }
-
-            //判断文件大小
-            long fileSize = file.getSize();
-
-            if (fileSize > answerMaxsize * 1048576) {
-                throw new StatusException("OfflineExamController-submitPaper-005", "文件大小不能超过" + answerMaxsize + "M");
+            String realMD5 = DigestUtils.md5Hex(file.getInputStream());
+            String tempMD5 = fileMd5Array[i];
+            if (!realMD5.equals(tempMD5)) {
+                log.warn("realMD5 = {}, tempMD5 = {}", realMD5, tempMD5);
+                throw new StatusException("400403", "文件MD5验证失败");
             }
 
-            File f = getUploadFile(file);
-            String strMd5 = MD5.md5Hex(f);
-            String summary = fileMd5Array[i];
-            if (!strMd5.equals(summary)) {
-                throw new StatusException("OfflineExamController-submitPaper-006", "文件数据不完整,请重新上传");
-            }
-
-            resultFiles.add(f);
+            String fileSuffix = FileUtil.getFileSuffix(file.getOriginalFilename());
+            FileInfo fileInfo = new FileInfo();
+            fileInfo.setOriginalFileName(file.getOriginalFilename());
+            fileInfo.setFileBytes(file.getBytes());
+            fileInfo.setFileSize(file.getSize());
+            fileInfo.setFileSuffix(fileSuffix);
+            fileInfo.setMd5(realMD5);
+            fileInfoList.add(fileInfo);
         }
 
-        offlineExamService.batchSubmitPaper(examRecordDataId, resultFiles, fileType);
-
-        //最后删除临时文件
-        for (File tempFile : resultFiles) {
-            tempFile.delete();
-        }
-        User user = getAccessUser();
-        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-上传作答", "考试记录ID:" + examRecordDataId + " 考生ID:" + examRecordData.getExamStudentId()));
-    }
-
-    private File getUploadFile(MultipartFile file) {
-        //建临时文件夹
-        File dirFile = new File(TEMP_FILE_EXP);
-        if (!dirFile.exists()) {
-            dirFile.mkdirs();
-        }
-        String fileName = file.getOriginalFilename();
-        File tempFile = new File(TEMP_FILE_EXP + fileName);
-        OutputStream os = null;
-        try {
-            os = new FileOutputStream(tempFile);
-            IOUtils.copyLarge(file.getInputStream(), os);
-        } catch (FileNotFoundException e) {
-            e.printStackTrace();
-        } catch (IOException e) {
-            e.printStackTrace();
-        } finally {
-            IOUtils.closeQuietly(os);
-        }
-        return tempFile;
+        offlineExamService.batchSubmitPaper(examRecordDataId, fileInfoList, fileType, user.getUserId());
     }
 
 }

+ 0 - 4
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/PracticeController.java

@@ -2,7 +2,6 @@ package cn.com.qmth.examcloud.core.oe.admin.api.controller;
 
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
-import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataSyncService;
 import cn.com.qmth.examcloud.core.oe.admin.service.PracticeService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.PracticeCourseInfo;
@@ -45,7 +44,6 @@ public class PracticeController extends ControllerSupport {
     @ApiOperation(value = "练习课程列表")
     @GetMapping("/queryPracticeCourseList")
     public List<PracticeCourseInfo> queryPracticeCourseList(@RequestParam Long examId) {
-        Check.isNull(examId, "examId不能为空");
         User user = getAccessUser();
         return practiceService.queryPracticeCourseList(examId, user.getUserId());
     }
@@ -59,7 +57,6 @@ public class PracticeController extends ControllerSupport {
     @ApiOperation(value = "课程练习记录详情")
     @GetMapping("/queryPracticeRecordList")
     public List<PracticeRecordInfo> queryPracticeRecordList(@RequestParam Long examStudentId) {
-        Check.isNull(examStudentId, "examStudentId不能为空");
         return practiceService.queryPracticeRecordList(examStudentId);
     }
 
@@ -71,7 +68,6 @@ public class PracticeController extends ControllerSupport {
     @ApiOperation(value = "单次练习答题情况统计")
     @GetMapping("/getPracticeDetailInfo")
     public PracticeDetailInfo getPracticeDetailInfo(@RequestParam Long examRecordDataId, @RequestParam(required = false) String fromCache) {
-        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
         if (fromCache != null) {
             examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
             if (examRecordDataId == null) {

+ 228 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/controller/client/ExamProcessController.java

@@ -0,0 +1,228 @@
+package cn.com.qmth.examcloud.core.oe.admin.api.controller.client;
+
+import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.FileUtil;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordDataBeanConvert;
+import cn.com.qmth.examcloud.core.oe.admin.api.bean.ExamRecordDataDomain;
+import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordPaperStructEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordQuestionsEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.uploadfile.FileInfo;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Api(tags = "(客户端)考试过程相关接口")
+@RestController
+@RequestMapping("${$rmp.ctr.oe}/client/exam/process")
+public class ExamProcessController extends ControllerSupport {
+
+    private static final Logger log = LoggerFactory.getLogger(ExamProcessController.class);
+
+    @Autowired
+    private ExamStudentService examStudentService;
+
+    @Autowired
+    private PracticeService practiceService;
+
+    @Autowired
+    private ExamRecordDataSyncService examRecordDataSyncService;
+
+    @Autowired
+    private ExamRecordPaperStructService examRecordPaperStructService;
+
+    @Autowired
+    private ExamRecordQuestionsService examRecordQuestionsService;
+
+    @Autowired
+    private ExamRecordDataRepo examRecordDataRepo;
+
+    @Autowired
+    private ExamScoreService examScoreService;
+
+    @Autowired
+    private OfflineExamService offlineExamService;
+
+    @ApiOperation(value = "获取在线考试待考列表")
+    @PostMapping("/queryExamList")
+    public List<OnHandExamInfo> queryExamList() {
+        User user = getAccessUser();
+        return examStudentService.queryOnlineExamList(user.getUserId(), ExamType.ONLINE);
+    }
+
+    @ApiOperation(value = "获取在线考试已结束列表")
+    @PostMapping("/queryExamEndList")
+    public List<OnHandExamInfo> queryExamEndList() {
+        User user = getAccessUser();
+        return examStudentService.queryOnlineExamEndList(user.getUserId(), ExamType.ONLINE);
+    }
+
+    @ApiOperation(value = "获取在线作业待考列表")
+    @PostMapping("/queryHomeworkList")
+    public List<OnHandExamInfo> queryHomeworkList() {
+        User user = getAccessUser();
+        return examStudentService.queryOnlineExamList(user.getUserId(), ExamType.ONLINE_HOMEWORK);
+    }
+
+    @ApiOperation(value = "练习课程列表")
+    @PostMapping("/queryPracticeCourseList")
+    public List<PracticeCourseInfo> queryPracticeCourseList(@RequestParam Long examId) {
+        User user = getAccessUser();
+        return practiceService.queryPracticeCourseList(examId, user.getUserId());
+    }
+
+    @ApiOperation(value = "课程练习记录详情")
+    @PostMapping("/queryPracticeRecordList")
+    public List<PracticeRecordInfo> queryPracticeRecordList(@RequestParam Long examStudentId) {
+        return practiceService.queryPracticeRecordList(examStudentId);
+    }
+
+    @ApiOperation(value = "单次练习答题情况统计")
+    @PostMapping("/getPracticeDetailInfo")
+    public PracticeDetailInfo getPracticeDetailInfo(@RequestParam Long examRecordDataId, @RequestParam(required = false) String fromCache) {
+        if (fromCache != null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if (examRecordDataId == null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+        return practiceService.getPracticeDetailInfo(examRecordDataId);
+    }
+
+    @ApiOperation(value = "获取考试记录试卷结构")
+    @PostMapping("/getExamRecordPaperStruct")
+    public ExamRecordPaperStructEntity getExamRecordPaperStruct(@RequestParam Long examRecordDataId, @RequestParam(required = false) String fromCache) {
+        if (fromCache != null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if (examRecordDataId == null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+        return examRecordPaperStructService.getExamRecordPaperStruct(examRecordDataId);
+    }
+
+    @ApiOperation(value = "获取交卷之后的所有试题作答信息")
+    @PostMapping("/getExamRecordQuestions")
+    public ExamRecordQuestionsEntity getExamRecordQuestions(@RequestParam Long examRecordDataId, @RequestParam(required = false) String fromCache) {
+        if (fromCache != null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if (examRecordDataId == null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+        return examRecordQuestionsService.getExamRecordQuestionsAndFixExamRecordDataIfNecessary(examRecordDataId);
+    }
+
+    @ApiOperation(value = "获取当前考试记录信息")
+    @PostMapping("/findExamRecordDataEntity")
+    public ExamRecordDataDomain findExamRecordDataEntity(@RequestParam Long examRecordDataId, @RequestParam(required = false) String fromCache) {
+        if (fromCache != null) {
+            examRecordDataId = examRecordDataSyncService.getExamRecordDataIdByCacheId(examRecordDataId);
+            if (examRecordDataId == null) {
+                throw new StatusException("1001", "未找到数据");
+            }
+        }
+        ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
+        ExamRecordDataDomain domain = ExamRecordDataBeanConvert.copyFrom(examRecordDataEntity);
+        domain.setExamRecord(examRecordDataEntity);
+        return domain;
+    }
+
+    @ApiOperation(value = "根据examStudentId获取客观分信息")
+    @PostMapping("/queryObjectiveScoreList")
+    public List<ObjectiveScoreInfo> queryObjectiveScoreList(@RequestParam Long examStudentId) {
+        return examScoreService.queryObjectiveScoreList(examStudentId);
+    }
+
+    @ApiOperation(value = "获取离线考试列表")
+    @PostMapping("/getOfflineCourse")
+    public List<OfflineExamCourseInfo> getOfflineCourse() {
+        User user = getAccessUser();
+        return offlineExamService.getOfflineCourse(user.getUserId());
+    }
+
+    @ApiOperation(value = "离线考试:开始考试")
+    @PostMapping("/startOfflineExam")
+    public void startOfflineExam(@RequestParam long examStudentId) {
+        offlineExamService.startOfflineExam(examStudentId);
+    }
+
+    @ApiOperation(value = "离线考试:交卷")
+    @PostMapping("/submitPaper")
+    public void submitPaper(@RequestParam Long examRecordDataId,
+                            @RequestPart(value = "file", required = false) MultipartFile file) throws Exception {
+        Check.isNull(file, "上传文件不能为空");
+        User user = getAccessUser();
+
+        String fileSuffix = FileUtil.getFileSuffix(file.getOriginalFilename());
+        FileInfo fileInfo = new FileInfo();
+        fileInfo.setOriginalFileName(file.getOriginalFilename());
+        fileInfo.setFileBytes(file.getBytes());
+        fileInfo.setFileSize(file.getSize());
+        fileInfo.setFileSuffix(fileSuffix);
+
+        offlineExamService.submitPaper(examRecordDataId, fileInfo, user.getUserId());
+    }
+
+    @ApiOperation(value = "离线考试:多文件交卷")
+    @PostMapping("/batchSubmitPaper")
+    public void batchSubmitPaper(@ApiParam(value = "考试记录id") @RequestParam Long examRecordDataId,
+                                 @ApiParam(value = "文件类型:ZIP、PDF、IMAGE(单选、三选一)") @RequestParam String fileType,
+                                 @ApiParam(value = "文件MD5信息") @RequestParam String[] fileMd5Array,
+                                 @ApiParam(value = "文件数组") @RequestPart(required = false) MultipartFile[] fileArray) throws Exception {
+        Check.isNull(fileArray, "上传文件不能为空");
+        User user = getAccessUser();
+
+        if (fileArray.length != fileMd5Array.length) {
+            throw new StatusException("400401", "文件数量和文件MD5数量不匹配");
+        }
+
+        List<FileInfo> fileInfoList = new ArrayList<>();
+        for (int i = 0; i < fileArray.length; i++) {
+            MultipartFile file = fileArray[i];
+            log.info("OfflineExam file {} - {}", i + 1, file.getOriginalFilename());
+
+            if (file.getSize() > Constants.ANSWER_FILE_MAX_SIZE * Constants.M_SIZE) {
+                throw new StatusException("400402", "文件大小不能超过" + Constants.ANSWER_FILE_MAX_SIZE + "M");
+            }
+
+            String realMD5 = DigestUtils.md5Hex(file.getInputStream());
+            String tempMD5 = fileMd5Array[i];
+            if (!realMD5.equals(tempMD5)) {
+                log.warn("realMD5 = {}, tempMD5 = {}", realMD5, tempMD5);
+                throw new StatusException("400403", "文件MD5验证失败");
+            }
+
+            String fileSuffix = FileUtil.getFileSuffix(file.getOriginalFilename());
+            FileInfo fileInfo = new FileInfo();
+            fileInfo.setOriginalFileName(file.getOriginalFilename());
+            fileInfo.setFileBytes(file.getBytes());
+            fileInfo.setFileSize(file.getSize());
+            fileInfo.setFileSuffix(fileSuffix);
+            fileInfo.setMd5(realMD5);
+            fileInfoList.add(fileInfo);
+        }
+
+        offlineExamService.batchSubmitPaper(examRecordDataId, fileInfoList, fileType, user.getUserId());
+    }
+
+}

+ 16 - 2
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/ExamRecordCloudServiceProvider.java

@@ -11,6 +11,9 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordForMarkingService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
+import cn.com.qmth.examcloud.core.oe.student.api.request.ExistExamRecordDataReq;
+import cn.com.qmth.examcloud.core.oe.student.api.response.ExistExamRecordDataResp;
 import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
 import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestionStructure;
 import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestionUnit;
@@ -80,6 +83,8 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
     @Autowired
     private ExamRecordDataService examRecordDataService;
 
+    @Autowired
+    private ExamRecordDataCloudService examRecordDataCloudService;
 
     @Override
     @ApiOperation(value = "查询是否已经开考")
@@ -102,10 +107,19 @@ public class ExamRecordCloudServiceProvider extends ControllerSupport implements
             return cb.and(predicates.toArray(new Predicate[predicates.size()]));
         };
 
-        long count = examRecordDataRepo.count(specification);
+        Boolean existed = examRecordDataRepo.count(specification) > 0;
+        if (!existed) {
+            // 若不存在考试记录,则再去“考试记录临时表”中查询确认一次(未交卷情况)
+            ExistExamRecordDataReq existReq = new ExistExamRecordDataReq();
+            existReq.setExamId(req.getExamId());
+            existReq.setCourseId(req.getCourseId());
+            existReq.setStudentId(req.getStudentId());
+            ExistExamRecordDataResp resp = examRecordDataCloudService.existExamRecordData(existReq);
+            existed = resp.getExisted();
+        }
 
         CheckExamIsStartedResp resp = new CheckExamIsStartedResp();
-        resp.setIsStarted(count > 0);
+        resp.setIsStarted(existed);
         return resp;
     }
 

+ 89 - 0
examcloud-core-oe-admin-api-provider/src/main/java/cn/com/qmth/examcloud/core/oe/admin/api/provider/SyncExamDataCloudServiceProvider.java

@@ -23,7 +23,12 @@ import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -91,6 +96,9 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
     @Autowired
     private FaceBiopsyItemStepRepo faceBiopsyItemStepRepo;
 
+    @Autowired
+    private ExamFaceLiveVerifyRepo examFaceLiveVerifyRepo;
+
     @Autowired
     private ExamAuditService examAuditService;
 
@@ -114,6 +122,11 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
 
     @Autowired
     private RedisClient redisClient;
+    
+    @Autowired
+    private ExamCaptureCameraInfoRepo examCaptureCameraInfoRepo;
+    
+    
 
     /**
      * 同步考试记录数据
@@ -163,6 +176,11 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         if (hasWarn != null && hasWarn) {
             examAuditService.saveHeaderWarnAudit(realExamRecordDataId);
         }
+        
+        //切屏次数超限
+        if (transitionExamRecordData.getExceedMaxSwitchScreenCount()  != null && transitionExamRecordData.getExceedMaxSwitchScreenCount()) {
+            examAuditService.saveExceedMaxSwitchScreenCount(realExamRecordDataId);
+        }
 
         startTime = this.debugCost("3 同步考试记录表", transitionExamRecordDataId, startTime);
 
@@ -230,6 +248,9 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
             syncFaceBiopsy(req.getFaceBiopsy(), realExamRecordDataId);
         }
 
+        //同步C端活体检测记录
+        syncFaceLiveVerifyRecords(req.getFaceLiveVerifyRecords(), realExamRecordDataId);
+
         startTime = this.debugCost("10 同步face id活体检测数据", transitionExamRecordDataId, startTime);
 
         //同步考试记录对应的试卷结构
@@ -587,6 +608,29 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
         }
     }
 
+    private void syncFaceLiveVerifyRecords(List<FaceLiveVerifyBean> faceLiveVerifyRecords, Long examRecordDataId) {
+        if (CollectionUtils.isEmpty(faceLiveVerifyRecords)) {
+            return;
+        }
+
+        for (FaceLiveVerifyBean bean : faceLiveVerifyRecords) {
+            ExamFaceLiveVerifyEntity entity = new ExamFaceLiveVerifyEntity();
+
+            entity.setExamRecordDataId(examRecordDataId);
+            entity.setStatus(FaceLiveVerifyStatus.valueOf(bean.getStatus()));
+            entity.setFaceCount(bean.getFaceCount());
+            entity.setSimilarity(bean.getSimilarity());
+            entity.setRealness(bean.getRealness());
+            entity.setErrorMsg(bean.getErrorMsg());
+            entity.setProcessTime(bean.getProcessTime());
+            entity.setActions(bean.getActions());
+            entity.setCreationTime(bean.getCreationTime());
+            entity.setUpdateTime(bean.getUpdateTime());
+
+            examFaceLiveVerifyRepo.save(entity);
+        }
+    }
+
     private FaceBiopsyEntity copyFaceBiopsyFrom(FaceBiopsyBean bean, Long examRecordDataId) {
         FaceBiopsyEntity entity = new FaceBiopsyEntity();
         entity.setRootOrgId(bean.getRootOrgId());
@@ -692,9 +736,54 @@ public class SyncExamDataCloudServiceProvider extends ControllerSupport implemen
     private void syncExamCapture(List<ExamCaptureBean> examCaptures, Long examRecordDataId) {
         for (ExamCaptureBean bean : examCaptures) {
             ExamCaptureEntity examCaptureEntity = copyExamCaptureFrom(bean, examRecordDataId);
+            saveExamCaptureCameraInfo(examCaptureEntity);
+            examCaptureEntity.setCameraInfos(null);
             examCaptureRepo.saveAndFlush(examCaptureEntity);
         }
     }
+    private void saveExamCaptureCameraInfo(ExamCaptureEntity examCaptureEntity) {
+        if(StringUtils.isEmpty(examCaptureEntity.getCameraInfos())) {
+        	return;
+        }
+        try {
+        	JSONArray jsonArray = new JSONArray(examCaptureEntity.getCameraInfos());
+            for (int i = 0; i < jsonArray.length(); i++) {
+        		JSONObject jsonObject = (JSONObject) jsonArray.get(i);
+        		ExamCaptureCameraInfoEntity ci=new ExamCaptureCameraInfoEntity();
+        		ci.setExamRecordDataId(examCaptureEntity.getExamRecordDataId());
+        		ci.setDetail(getVal(jsonObject, "detail"));
+        		ci.setName(getVal(jsonObject, "name"));
+        		ci.setPid(getVal(jsonObject, "pid"));
+        		ci.setVid(getVal(jsonObject, "vid"));
+        		if (jsonObject.isNull("pid") || StringUtils.isBlank(jsonObject.getString("pid"))
+        				||jsonObject.isNull("vid") || StringUtils.isBlank(jsonObject.getString("vid"))) {
+        			ci.setVirtualCamera(true);
+        		}else {
+        			ci.setVirtualCamera(false);
+        		}
+        		List<ExamCaptureCameraInfoEntity> olds=examCaptureCameraInfoRepo.findByExamRecordDataIdAndPidAndVidAndName(examCaptureEntity.getExamRecordDataId(),ci.getPid(),ci.getVid(),ci.getName());
+        		if(CollectionUtils.isEmpty(olds)) {
+        			examCaptureCameraInfoRepo.saveAndFlush(ci);
+        		}else {
+        			ExamCaptureCameraInfoEntity old=olds.get(0);
+        			if(!StringUtils.equals(old.getDetail(), ci.getDetail())) {
+	        			old.setDetail(ci.getDetail());
+	        			examCaptureCameraInfoRepo.saveAndFlush(old);
+        			}
+        		}
+            }
+        } catch (Exception e) {
+        	LOG.error("保存摄像信息失败:"+examCaptureEntity.getExamRecordDataId()+" "+examCaptureEntity.getCameraInfos(), e);
+        }
+    }
+    
+    private String getVal(JSONObject jsonObject,String name) throws JSONException {
+    	if (jsonObject.isNull(name) || StringUtils.isBlank(jsonObject.getString(name))) {
+    		return "";
+    	}else {
+    		return jsonObject.getString(name);
+    	}
+    }
 
     private ExamCaptureEntity copyExamCaptureFrom(ExamCaptureBean examCapture, Long examRecordDataId) {
         ExamCaptureEntity entity = new ExamCaptureEntity();

+ 42 - 22
examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/Constants.java

@@ -1,10 +1,3 @@
-/*
- * *************************************************
- * Copyright (c) 2018 QMTH. All Rights Reserved.
- * Created by Deason on 2018-08-24 11:34:17.
- * *************************************************
- */
-
 package cn.com.qmth.examcloud.core.oe.admin.base;
 
 /**
@@ -34,22 +27,27 @@ public interface Constants {
      * 开始考试锁
      */
     String START_EXAM_LOCK_PREFIX = "oe_student:start_exam_lock_studentid_";
+
     /**
      * 结束考试锁前缀
      */
     String END_EXAM_LOCK_PREFIX = "oe_student:end_exam_lock_studentid_";
+
     /**
      * 考试交卷锁前缀
      */
     String HAND_IN_EXAM_LOCK_PREFIX = "oe_student:hand_in_exam_lock_";
+
     /**
      * 处理照片锁前缀
      */
     String EXAM_CAPTURE_PHOTO_LOCK_PREFIX = "oe_student:exam_capture_lock_";
+
     /**
      * 获取人脸活体检测基本信息前缀
      */
     String GET_FACE_BIOPSY_INFO_PREFIX = "oe_student:get_face_biopsy_info_lock_";
+
     //
     String ERROR_MSG = "error_message";
 
@@ -75,78 +73,89 @@ public interface Constants {
 
     //缓存
     String CACHE_EXAM_STUDENT_PREFIX = "cache_examStudent_";
+
     String CACHE_PHONE_PREFIX = "cache_phone_";
+
     String CACHE_STUDENT_PREFIX = "cache_student_";
+
     String CACHE_ORG_PREFIX = "cache_org_";
+
     String CACHE_COURSE_PREFIX = "cache_course_";
 
     String isTrue = "true";
 
     String isFalse = "false";
+
     /**
      * 考试成绩通知路径前缀
      */
-    String OE_EXAM_SCORE_NOTIFY_URL_PREFIX="oe.examScoreNotify.url.";
+    String OE_EXAM_SCORE_NOTIFY_URL_PREFIX = "oe.examScoreNotify.url.";
+
     /**
      * 考试成绩通知路径传输方法前缀
      */
-    String OE_EXAM_SCORE_NOTIFY_URL_HTTP_METHOD_PREFIX="oe.examScoreNotify.url.httpMethod.";
+    String OE_EXAM_SCORE_NOTIFY_URL_HTTP_METHOD_PREFIX = "oe.examScoreNotify.url.httpMethod.";
 
     //face++ 人脸比对相关错误详情
+
     /**
      * face++ 人脸比对API并发次数超过上限
      */
-    String FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED="CONCURRENCY_LIMIT_EXCEEDED";
+    String FACE_COMPARE_CONCURRENCY_LIMIT_EXCEEDED = "CONCURRENCY_LIMIT_EXCEEDED";
+
     /**
      * face++ api_key没有调用本API的权限,具体原因为:用户自己禁止该api_key调用、管理员禁止该api_key调用、由于账户余额不足禁止调用。
      */
-    String FACE_COMPARE_AUTHORIZATION_ERROR="AUTHORIZATION_ERROR";
+    String FACE_COMPARE_AUTHORIZATION_ERROR = "AUTHORIZATION_ERROR";
+
     /**
      * face++ 下载图片超时
      */
-    String FACE_COMPARE_IMAGE_DOWNLOAD_TIMEOUT="IMAGE_DOWNLOAD_TIMEOUT";
+    String FACE_COMPARE_IMAGE_DOWNLOAD_TIMEOUT = "IMAGE_DOWNLOAD_TIMEOUT";
 
 
     //百度活检错误码 http://ai.baidu.com/docs#/Face-Java-SDK/514d7ea4
     String BAIDU_ERROR_CODE = "error_code";
-    String BAIDU_ERROR_MSG="error_msg";
+
+    String BAIDU_ERROR_MSG = "error_msg";
+
     String BAIDU_SUCCESS_ERROR_CODE_VALUE = "0";
 
     /**
      * 连接超时或读取数据超时
      */
-    String BAIDU_FACELIVENESS_CONNECTION_OR_READ_DATA_TIME_OUT_CODE ="SDK108";
+    String BAIDU_FACELIVENESS_CONNECTION_OR_READ_DATA_TIME_OUT_CODE = "SDK108";
 
     /**
      * 百度在线活体检测QPS超过上限的错误码
      */
-    String BAIDU_FACELIVENESS_QPS_LIMIT_EXCEEDED_CODE ="18";
+    String BAIDU_FACELIVENESS_QPS_LIMIT_EXCEEDED_CODE = "18";
 
 
     /**
      * 抓拍照片又拍云签名前缀
      */
-    String EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX ="OE_EXAM_CAPTURE_PHOTO_UPYUN_SIGN_";
+    String EXAM_CAPTURE_PHOTO_UPYUN_SIGN_PREFIX = "OE_EXAM_CAPTURE_PHOTO_UPYUN_SIGN_";
 
     /**
      * 处理照片高优先级
      */
-    int PROCESS_CAPTURE_HIGH_PRIORITY=1;
+    int PROCESS_CAPTURE_HIGH_PRIORITY = 1;
 
     /**
      * 照片处理中状态码
      */
-    String CAPTURE_PROCESSING_STATUS_CODE="101222";
+    String CAPTURE_PROCESSING_STATUS_CODE = "101222";
 
     /**
      * 考试未结束状态码
      */
-    String EXAM_RECORD_NOT_END_STATUS_CODE="101333";
+    String EXAM_RECORD_NOT_END_STATUS_CODE = "101333";
 
     /**
      * 交卷处理中
      */
-    String PROCESSING_EXAM_RECORD_CODE="S-101000";
+    String PROCESSING_EXAM_RECORD_CODE = "S-101000";
 
     /**
      * 通用成功编码
@@ -161,5 +170,16 @@ public interface Constants {
     /**
      * 活体检测方案key
      */
-    String IDENTIFICATION_OF_LIVING_BODY_SCHEME_KEY ="IDENTIFICATION_OF_LIVING_BODY_SCHEME";
-}
+    String IDENTIFICATION_OF_LIVING_BODY_SCHEME_KEY = "IDENTIFICATION_OF_LIVING_BODY_SCHEME";
+
+    /**
+     * 答案文件最大限制,单位:M
+     */
+    long ANSWER_FILE_MAX_SIZE = 30;
+
+    /**
+     * 1024 * 1024
+     */
+    long M_SIZE = 1048576;
+
+}

+ 33 - 0
examcloud-core-oe-admin-base/src/main/java/cn/com/qmth/examcloud/core/oe/admin/base/utils/RoleUtil.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.examcloud.core.oe.admin.base.utils;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.commons.collections4.CollectionUtils;
+
+import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
+import cn.com.qmth.examcloud.core.basic.api.bean.RoleBean;
+import cn.com.qmth.examcloud.core.basic.api.bean.UserBean;
+
+
+public class RoleUtil {
+
+	public static boolean hasAnyRoles(UserBean accessUser, RoleMeta... roles) {
+		List<RoleBean> roleList = accessUser.getRoleList();
+		if (CollectionUtils.isEmpty(roleList)) {
+			return false;
+		}
+
+		Set<String> roleCodes = roleList.stream().map(e -> e.getRoleCode()).collect(Collectors.toSet());
+		for (RoleMeta role : roles) {
+			if (roleCodes.contains(role.name())) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+
+
+}

+ 27 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureCameraInfoRepo.java

@@ -0,0 +1,27 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import java.util.List;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamCaptureCameraInfoEntity;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 10:23
+ * @company QMTH
+ * @description ExamCaptureEntityRepo
+ */
+@Repository
+public interface ExamCaptureCameraInfoRepo extends JpaRepository<ExamCaptureCameraInfoEntity, Long>, JpaSpecificationExecutor<ExamCaptureCameraInfoEntity> {
+
+	List<ExamCaptureCameraInfoEntity> findByExamRecordDataIdAndVirtualCamera(Long examRecordDataId, boolean b);
+
+	List<ExamCaptureCameraInfoEntity> findByExamRecordDataIdInAndVirtualCamera(List<Long> ids, boolean b);
+
+	List<ExamCaptureCameraInfoEntity> findByExamRecordDataIdAndPidAndVidAndName(Long examRecordDataId, String pid, String vid,
+			String name);
+	
+}

+ 7 - 5
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamCaptureQueueRepo.java

@@ -1,14 +1,13 @@
 package cn.com.qmth.examcloud.core.oe.admin.dao;
 
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamCaptureQueueEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamCaptureQueueStatus;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.stereotype.Repository;
 
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamCaptureQueueEntity;
-import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamCaptureQueueStatus;
-
 import javax.transaction.Transactional;
 import java.util.Date;
 import java.util.List;
@@ -19,6 +18,7 @@ import java.util.List;
  * @company QMTH
  * @description ExamCaptureQueueRepo
  */
+@Deprecated
 @Repository
 public interface ExamCaptureQueueRepo extends JpaRepository<ExamCaptureQueueEntity, Long>, JpaSpecificationExecutor<ExamCaptureQueueEntity> {
 
@@ -101,15 +101,17 @@ public interface ExamCaptureQueueRepo extends JpaRepository<ExamCaptureQueueEnti
     @Modifying
     @Query(value = "update ec_oe_exam_capture_queue set is_pass=?2,is_stranger=?3,`status`=?4,face_compare_result=?5,face_compare_start_time=?6,update_time=?7 where id=?1", nativeQuery = true)
     void saveExamCaptureQueueEntityBySuccessful(Long captureQueueId, Boolean isPass, Boolean isStranger, String status,
-                                                String faceCompareResult,Long faceCompareStartTime,Date updateDate);
+                                                String faceCompareResult, Long faceCompareStartTime, Date updateDate);
 
     /**
      * 更新批次号
+     *
      * @param captureQueueId 队列id
-     * @param batchNum 批次号
+     * @param batchNum       批次号
      */
     @Transactional
     @Modifying
     @Query(value = "update ec_oe_exam_capture_queue set process_batch_num=?2 where id=?1", nativeQuery = true)
     void updateProcessBatchNum(Long captureQueueId, String batchNum);
+
 }

+ 15 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamFaceLiveVerifyRepo.java

@@ -0,0 +1,15 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamFaceLiveVerifyEntity;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ExamFaceLiveVerifyRepo extends JpaRepository<ExamFaceLiveVerifyEntity, Long>, JpaSpecificationExecutor<ExamFaceLiveVerifyEntity> {
+
+    List<ExamFaceLiveVerifyEntity> findByExamRecordDataId(Long examRecordDataId);
+
+}

+ 4 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamProcessRecordRepo.java

@@ -18,4 +18,8 @@ public interface ExamProcessRecordRepo
         extends JpaRepository<ExamProcessRecordEntity, Long>, JpaSpecificationExecutor<ExamProcessRecordEntity> {
 
     List<ExamProcessRecordEntity> findByExamRecordDataIdOrderByRecordTimeAsc(Long examRecordDataId);
+
+	List<ExamProcessRecordEntity> findByExamRecordDataId(Long examRecordDataId);
+
+	List<ExamProcessRecordEntity> findByExamRecordDataIdIn(List<Long> ids);
 }

+ 10 - 1
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/ExamRecordDataRepo.java

@@ -1,13 +1,16 @@
 package cn.com.qmth.examcloud.core.oe.admin.dao;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
 import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -104,7 +107,13 @@ public interface ExamRecordDataRepo extends JpaRepository<ExamRecordDataEntity,
             "where record_data.exam_record_status in('EXAM_END','EXAM_OVERDUE') and record_data.exam_id=?1 " +
             "AND record_data.is_audit=0 ", nativeQuery = true)
     List<ExamRecordDataEntity> findDataByExamId(Long examId);
-    
+
     @Query(value = "select t.id from ec_oe_exam_record_data t where t.base_paper_id=?1 limit 1", nativeQuery = true)
     Long getRecordIdByPaperId(String basePaperId);
+
+    @Transactional
+    @Modifying
+    @Query("update ExamRecordDataEntity set examRecordStatus = :examRecordStatus, endTime = :endTime where id = :id")
+    int updateExamRecordStatusById(@Param("id") long id, @Param("examRecordStatus") ExamRecordStatus examRecordStatus, @Param("endTime") Date endTime);
+
 }

+ 113 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureCameraInfoEntity.java

@@ -0,0 +1,113 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Index;
+import javax.persistence.Table;
+
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+/**
+ * @author chenken
+ * @date 2018/8/15 10:19
+ * @company QMTH
+ * @description ExamCaptureEntity
+ */
+@Entity
+@Table(name = "ec_oe_exam_capture_camera_info", indexes = {
+        @Index(name = "IDX_OE_CCI_001", columnList = "examRecordDataId"),
+        @Index(name = "IDX_OE_CCI_002", columnList = "name"),
+        @Index(name = "IDX_OE_CCI_003", columnList = "examRecordDataId,pid,vid,name")
+})
+public class ExamCaptureCameraInfoEntity extends JpaEntity {
+
+
+    /**
+	 * 
+	 */
+	private static final long serialVersionUID = 5768699298680442068L;
+
+	@Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+	@Column(nullable = false)
+    private Long examRecordDataId;
+
+    @Column(name = "virtual_camera",nullable = false)
+    private Boolean virtualCamera;
+
+    @Column(name = "detail", length = 2000)
+    private String detail;
+    
+    @Column(name = "pid", length = 200,nullable = false)
+    private String pid;
+    
+    @Column(name = "vid", length = 200,nullable = false)
+    private String vid;
+    
+    @Column(name = "name", length = 200,nullable = false)
+    private String name;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Long getExamRecordDataId() {
+		return examRecordDataId;
+	}
+
+	public void setExamRecordDataId(Long examRecordDataId) {
+		this.examRecordDataId = examRecordDataId;
+	}
+
+
+	public Boolean getVirtualCamera() {
+		return virtualCamera;
+	}
+
+	public void setVirtualCamera(Boolean virtualCamera) {
+		this.virtualCamera = virtualCamera;
+	}
+
+	public String getDetail() {
+		return detail;
+	}
+
+	public void setDetail(String detail) {
+		this.detail = detail;
+	}
+
+	public String getPid() {
+		return pid;
+	}
+
+	public void setPid(String pid) {
+		this.pid = pid;
+	}
+
+	public String getVid() {
+		return vid;
+	}
+
+	public void setVid(String vid) {
+		this.vid = vid;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+
+}

+ 6 - 9
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureEntity.java

@@ -23,13 +23,16 @@ public class ExamCaptureEntity extends JpaEntity {
      *
      */
     private static final long serialVersionUID = -6162329876793785782L;
+
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
+
     /**
      * ec_oe_exam_record_data  ID
      */
     private Long examRecordDataId;
+
     /**
      * 文件URL
      */
@@ -55,11 +58,13 @@ public class ExamCaptureEntity extends JpaEntity {
      */
     @Column(name = "is_stranger")
     private Boolean isStranger;
+
     /**
      * 百度人脸关键点坐标结果
      */
-    @Column(name = "landmark",length = 4000)
+    @Column(name = "landmark", length = 4000)
     private String landmark;
+
     /**
      * 百度在线活体检测结果
      */
@@ -95,14 +100,6 @@ public class ExamCaptureEntity extends JpaEntity {
     @Column(name = "ext_msg", length = 800)
     private String extMsg;
 
-    public ExamCaptureEntity() {
-    }
-
-    public ExamCaptureEntity(ExamCaptureQueueEntity examCaptureQueueEntity) {
-        this.examRecordDataId = examCaptureQueueEntity.getExamRecordDataId();
-        this.fileUrl = examCaptureQueueEntity.getFileUrl();
-    }
-
     public Long getId() {
         return id;
     }

+ 66 - 64
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamCaptureQueueEntity.java

@@ -1,12 +1,9 @@
 package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
 
-import javax.persistence.*;
-
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamCaptureQueueStatus;
-import cn.com.qmth.examcloud.web.jpa.JpaEntity;
 import cn.com.qmth.examcloud.web.jpa.WithIdJpaEntity;
 
-import java.util.Date;
+import javax.persistence.*;
 
 /**
  * @author chenken
@@ -15,15 +12,17 @@ import java.util.Date;
  * @description 抓拍队列
  */
 @Entity
-@Table(name="ec_oe_exam_capture_queue",indexes = {
-        @Index(name = "IDX_E_O_E_C_Q_001",columnList = "examRecordDataId,fileName",unique = true),
-        @Index(name = "IDX_E_O_E_C_Q_002",columnList = "status,errorNum")
+@Table(name = "ec_oe_exam_capture_queue", indexes = {
+        @Index(name = "IDX_E_O_E_C_Q_001", columnList = "examRecordDataId,fileName", unique = true),
+        @Index(name = "IDX_E_O_E_C_Q_002", columnList = "status,errorNum")
 })
+@Deprecated
 public class ExamCaptureQueueEntity extends WithIdJpaEntity {
+
     /**
-	 * 
-	 */
-	private static final long serialVersionUID = 4094671807731989565L;
+     *
+     */
+    private static final long serialVersionUID = 4094671807731989565L;
 
     private Long studentId;
 
@@ -31,8 +30,8 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
      * ec_oe_exam_record_data  ID
      */
     private Long examRecordDataId;
-    
-    
+
+
     /**
      * 底照Token
      */
@@ -42,7 +41,7 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
      * 文件URL
      */
     private String fileUrl;
-    
+
     /**
      * 文件名称
      */
@@ -58,7 +57,7 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
      * 错误信息
      */
     private String errorMsg;
-    
+
     /**
      * 错误次数
      */
@@ -67,23 +66,23 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
     /**
      * 是否存在虚拟摄像头
      */
-    @Column(name="has_virtual_camera")
+    @Column(name = "has_virtual_camera")
     private Boolean hasVirtualCamera;
-    
+
     /**
      * 摄像头信息  json字符串数组
      */
     @Column(name = "camera_infos", length = 800)
     private String cameraInfos;
-    
+
     /**
      * 其他信息
      * Json格式
      * {
-     * 	"":""
+     * "":""
      * }
      */
-    @Column(name="ext_msg",length=800)
+    @Column(name = "ext_msg", length = 800)
     private String extMsg;
 
     /**
@@ -94,7 +93,7 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
     /**
      * 队列处理的优先级,默认值为0
      */
-    private int priority=0;
+    private int priority = 0;
 
     /**
      * 是否有陌生人
@@ -102,6 +101,7 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
      */
     @Column(name = "is_stranger")
     private Boolean isStranger;
+
     /**
      * 比较是否通过
      */
@@ -111,8 +111,9 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
     /**
      * 人脸比较返回信息
      */
-    @Column(name = "face_compare_result",length = 2000)
+    @Column(name = "face_compare_result", length = 2000)
     private String faceCompareResult;
+
     /**
      * 人脸比对开始时间
      */
@@ -121,7 +122,7 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
     /**
      * 百度在线活体检测结果
      */
-    @Column(name = "faceliveness_result",length = 2000)
+    @Column(name = "faceliveness_result", length = 2000)
     private String facelivenessResult;
 
     public Long getExamRecordDataId() {
@@ -156,61 +157,61 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
         this.errorMsg = errorMsg;
     }
 
-	public String getBaseFaceToken() {
-		return baseFaceToken;
-	}
+    public String getBaseFaceToken() {
+        return baseFaceToken;
+    }
 
-	public void setBaseFaceToken(String baseFaceToken) {
-		this.baseFaceToken = baseFaceToken;
-	}
+    public void setBaseFaceToken(String baseFaceToken) {
+        this.baseFaceToken = baseFaceToken;
+    }
 
-	public String getFileName() {
-		return fileName;
-	}
+    public String getFileName() {
+        return fileName;
+    }
 
-	public void setFileName(String fileName) {
-		this.fileName = fileName;
-	}
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
 
-	public Integer getErrorNum() {
-		return errorNum;
-	}
+    public Integer getErrorNum() {
+        return errorNum;
+    }
 
-	public void setErrorNum(Integer errorNum) {
-		this.errorNum = errorNum;
-	}
+    public void setErrorNum(Integer errorNum) {
+        this.errorNum = errorNum;
+    }
 
-	public Long getStudentId() {
-		return studentId;
-	}
+    public Long getStudentId() {
+        return studentId;
+    }
 
-	public void setStudentId(Long studentId) {
-		this.studentId = studentId;
-	}
+    public void setStudentId(Long studentId) {
+        this.studentId = studentId;
+    }
 
-	public Boolean getHasVirtualCamera() {
-		return hasVirtualCamera;
-	}
+    public Boolean getHasVirtualCamera() {
+        return hasVirtualCamera;
+    }
 
-	public void setHasVirtualCamera(Boolean hasVirtualCamera) {
-		this.hasVirtualCamera = hasVirtualCamera;
-	}
+    public void setHasVirtualCamera(Boolean hasVirtualCamera) {
+        this.hasVirtualCamera = hasVirtualCamera;
+    }
 
-	public String getCameraInfos() {
-		return cameraInfos;
-	}
+    public String getCameraInfos() {
+        return cameraInfos;
+    }
 
-	public void setCameraInfos(String cameraInfos) {
-		this.cameraInfos = cameraInfos;
-	}
+    public void setCameraInfos(String cameraInfos) {
+        this.cameraInfos = cameraInfos;
+    }
 
-	public String getExtMsg() {
-		return extMsg;
-	}
+    public String getExtMsg() {
+        return extMsg;
+    }
 
-	public void setExtMsg(String extMsg) {
-		this.extMsg = extMsg;
-	}
+    public void setExtMsg(String extMsg) {
+        this.extMsg = extMsg;
+    }
 
     public String getProcessBatchNum() {
         return processBatchNum;
@@ -267,4 +268,5 @@ public class ExamCaptureQueueEntity extends WithIdJpaEntity {
     public void setFacelivenessResult(String facelivenessResult) {
         this.facelivenessResult = facelivenessResult;
     }
+
 }

+ 141 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamFaceLiveVerifyEntity.java

@@ -0,0 +1,141 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.entity;
+
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.FaceLiveVerifyStatus;
+import cn.com.qmth.examcloud.web.jpa.JpaEntity;
+
+import javax.persistence.*;
+
+/**
+ * 人脸活体验证结果表(支持C端客户端活检)
+ */
+@Entity
+@Table(name = "ec_oe_exam_face_live_verify", indexes = {
+        @Index(name = "IDX_OE_FLV_01", columnList = "examRecordDataId"),
+        @Index(name = "IDX_OE_FLV_02", columnList = "status")
+})
+public class ExamFaceLiveVerifyEntity extends JpaEntity {
+
+    private static final long serialVersionUID = -3428631813990503829L;
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    /**
+     * 考试记录ID
+     */
+    @Column(nullable = false)
+    private Long examRecordDataId;
+
+    /**
+     * 验证状态
+     */
+    @Enumerated(EnumType.STRING)
+    @Column(length = 50, nullable = false)
+    private FaceLiveVerifyStatus status;
+
+    /**
+     * 动作验证列表,JSON格式
+     */
+    @Column(length = 2000)
+    private String actions;
+
+    /**
+     * 人脸数量
+     */
+    private Integer faceCount;
+
+    /**
+     * 相似度分数
+     */
+    private Double similarity;
+
+    /**
+     * 真实性分数
+     */
+    private Double realness;
+
+    /**
+     * 处理耗时(毫秒)
+     */
+    private Long processTime;
+
+    /**
+     * 错误信息
+     */
+    @Column(length = 500)
+    private String errorMsg;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getExamRecordDataId() {
+        return examRecordDataId;
+    }
+
+    public void setExamRecordDataId(Long examRecordDataId) {
+        this.examRecordDataId = examRecordDataId;
+    }
+
+    public FaceLiveVerifyStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(FaceLiveVerifyStatus status) {
+        this.status = status;
+    }
+
+    public String getActions() {
+        return actions;
+    }
+
+    public void setActions(String actions) {
+        this.actions = actions;
+    }
+
+    public Integer getFaceCount() {
+        return faceCount;
+    }
+
+    public void setFaceCount(Integer faceCount) {
+        this.faceCount = faceCount;
+    }
+
+    public Double getSimilarity() {
+        return similarity;
+    }
+
+    public void setSimilarity(Double similarity) {
+        this.similarity = similarity;
+    }
+
+    public Double getRealness() {
+        return realness;
+    }
+
+    public void setRealness(Double realness) {
+        this.realness = realness;
+    }
+
+    public Long getProcessTime() {
+        return processTime;
+    }
+
+    public void setProcessTime(Long processTime) {
+        this.processTime = processTime;
+    }
+
+    public String getErrorMsg() {
+        return errorMsg;
+    }
+
+    public void setErrorMsg(String errorMsg) {
+        this.errorMsg = errorMsg;
+    }
+
+}

+ 5 - 8
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExamSyncCaptureEntity.java

@@ -23,13 +23,16 @@ public class ExamSyncCaptureEntity extends JpaEntity {
      *
      */
     private static final long serialVersionUID = -6162329876793785782L;
+
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;
+
     /**
      * ec_oe_exam_record_data  ID
      */
     private Long examRecordDataId;
+
     /**
      * 文件URL
      */
@@ -55,11 +58,13 @@ public class ExamSyncCaptureEntity extends JpaEntity {
      */
     @Column(name = "is_stranger")
     private Boolean isStranger;
+
     /**
      * 百度人脸关键点坐标结果
      */
     @Column(name = "landmark", length = 4000)
     private String landmark;
+
     /**
      * 百度在线活体检测结果
      */
@@ -95,14 +100,6 @@ public class ExamSyncCaptureEntity extends JpaEntity {
     @Column(name = "ext_msg", length = 800)
     private String extMsg;
 
-    public ExamSyncCaptureEntity() {
-    }
-
-    public ExamSyncCaptureEntity(ExamCaptureQueueEntity examCaptureQueueEntity) {
-        this.examRecordDataId = examCaptureQueueEntity.getExamRecordDataId();
-        this.fileUrl = examCaptureQueueEntity.getFileUrl();
-    }
-
     public Long getId() {
         return id;
     }

+ 1 - 1
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/entity/ExportTaskEntity.java

@@ -82,7 +82,7 @@ public class ExportTaskEntity extends JpaEntity {
     /**
      * 导出的参数
      */
-    @Column
+    @Column(length = 1000)
     private String exportParam;
     
     /**

+ 34 - 30
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/DisciplineType.java

@@ -5,53 +5,57 @@ package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
  */
 public enum DisciplineType {
 
-	CHEAT(1, "作弊"),
+    CHEAT(1, "作弊"),
 
-	NOT_ONESELF(2, "他人替考"),
+    NOT_ONESELF(2, "他人替考"),
 
-	NOT_ONESELF_OF_PHOTO(3, "相片替考"),
+    NOT_ONESELF_OF_PHOTO(3, "相片替考"),
 
-	ACTION_FAILURE(4, "指定动作失败"),
+    ACTION_FAILURE(4, "指定动作失败"),
 
-	BATCH_NOTPASS(5, "批量审核不通过"),
+    BATCH_NOTPASS(5, "批量审核不通过"),
 
-	BATCH_PASS(6, "批量审核通过"),
+    BATCH_PASS(6, "批量审核通过"),
 
-	POWER_FAILURE(7, "断电"),
+    POWER_FAILURE(7, "断电"),
 
-	WITHOUT_ACTION(8, "未进行人脸指定动作检测"),
+    WITHOUT_ACTION(8, "未进行人脸指定动作检测"),
 
-	MACHINE_STOPPAGE(9, "机器故障"),
+    MACHINE_STOPPAGE(9, "机器故障"),
 
-	OTHER(10, "其他"),
+    OTHER(10, "其他"),
 
-	ILLEGAL_DATA(11, "非法数据"),
+    ILLEGAL_DATA(11, "非法数据"),
 
-	ILLEGAL_CLIENT(12, "非法考生端应用");
+    ILLEGAL_CLIENT(12, "非法考生端应用"),
+    
+    EXCEED_MAX_SWITCH_SCREEN_COUNT(13, "超过切屏次数限制")
+    
+    ;
 
-	private Integer sortNo;
+    private Integer sortNo;
 
-	private String description;
+    private String description;
 
-	DisciplineType(Integer sortNo, String description) {
-		this.sortNo = sortNo;
-		this.description = description;
-	}
+    DisciplineType(Integer sortNo, String description) {
+        this.sortNo = sortNo;
+        this.description = description;
+    }
 
-	public Integer getSortNo() {
-		return sortNo;
-	}
+    public Integer getSortNo() {
+        return sortNo;
+    }
 
-	public void setSortNo(Integer sortNo) {
-		this.sortNo = sortNo;
-	}
+    public void setSortNo(Integer sortNo) {
+        this.sortNo = sortNo;
+    }
 
-	public String getDescription() {
-		return description;
-	}
+    public String getDescription() {
+        return description;
+    }
 
-	public void setDescription(String description) {
-		this.description = description;
-	}
+    public void setDescription(String description) {
+        this.description = description;
+    }
 
 }

+ 0 - 41
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/FaceBiopsyScheme.java

@@ -1,41 +0,0 @@
-package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
-
-/**
- * @Description 活体检测方案枚举
- * @Author lideyin
- * @Date 2019/11/6 15:03
- * @Version 1.0
- */
-public enum FaceBiopsyScheme {
-    /**
-     * FaceID活体检测方案(即旧活体检测方案)
-     */
-    FACE_ID("S1", "FaceID活体检测方案"),
-    /**
-     * 新活体检测方案(暂时无法给出具体命名,以后有需要再改动)
-     */
-    NEW("S2", "新活体检测方案");
-    private String code;
-    private String desc;
-
-    FaceBiopsyScheme(String code, String desc) {
-        this.code = code;
-        this.desc = desc;
-    }
-
-    /**
-     * 获取活检方案代码
-     * @return
-     */
-    public String getCode(){
-        return this.code;
-    }
-
-    /**
-     * 获取活检方案描述
-     * @return
-     */
-    public String getDesc(){
-        return this.desc;
-    }
-}

+ 6 - 2
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/FaceBiopsyType.java

@@ -1,4 +1,5 @@
 package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
+
 /*
  * @Description 人脸活体检测类型
  * @Author lideyin
@@ -6,12 +7,15 @@ package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
  * @Version 1.0
  */
 public enum FaceBiopsyType {
+
     /**
-     * faceid人脸活体识别技术
+     * FaceID活体检测方案(即旧活体检测方案)
      */
     FACE_ID,
+
     /**
-     * 人脸移动识别技术
+     * Electron Client 自研活体检测方案
      */
     FACE_MOTION
+
 }

+ 28 - 0
examcloud-core-oe-admin-dao/src/main/java/cn/com/qmth/examcloud/core/oe/admin/dao/enums/FaceLiveVerifyStatus.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.oe.admin.dao.enums;
+
+/**
+ * 人脸活体验证状态
+ */
+public enum FaceLiveVerifyStatus {
+
+    SUCCESS("验证成功"),
+
+    ACTION_FAILED("动作有误,验证失败"),
+
+    NOT_ONESELF("不是本人"),
+
+    TIME_OUT("超时未完成"),
+
+    ERROR("验证异常");
+
+    private String description;
+
+    FaceLiveVerifyStatus(String description) {
+        this.description = description;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+}

+ 6 - 8
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamAuditService.java

@@ -9,6 +9,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service;
 
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.RedoAuditInfo;
@@ -30,15 +31,8 @@ public interface ExamAuditService {
      * @param query
      * @return
      */
-    Page<ExamAuditInfo> getExamAuditList(ExamAuditQuery query);
+    Page<ExamAuditInfo> getExamAuditList(UserDataRules uds,ExamAuditQuery query);
 
-    /**
-     * 查询监考已审列表
-     *
-     * @param query
-     * @return
-     */
-    List<ExamAuditInfo> getExamAudit(ExamAuditQuery query);
 
 
     /**
@@ -96,4 +90,8 @@ public interface ExamAuditService {
 
     void saveExamAuditForIllegalClient(Long realExamRecordDataId, String reason);
 
+	void saveExceedMaxSwitchScreenCount(Long realExamRecordDataId);
+
+	List<ExamAuditInfo> getExamAudit(UserDataRules uds, ExamAuditQuery query);
+
 }

+ 2 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamProcessRecordService.java

@@ -19,5 +19,7 @@ public interface ExamProcessRecordService {
      * @return
      */
     List<ExamProcessRecordInfo> getExamProcessRecords(Long examRecordDataId);
+    
+    String getIps(Long examRecordDataId);
 
 }

+ 0 - 6
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordForMarkingService.java

@@ -34,13 +34,7 @@ public interface ExamRecordForMarkingService {
 
     /**
      * 离线考试-保存阅卷相关数据-对内
-     *
-     * @param examRecordData
-     * @param fileName
-     * @param fileUrl
      */
-    void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData, String fileName, String fileUrl);
-
     void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData);
 
     /**

+ 22 - 21
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamRecordService.java

@@ -9,6 +9,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamStudentEffectiveScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamStudentQuestionScoreInfo;
@@ -30,7 +31,7 @@ public interface ExamRecordService {
      * @param query
      * @return
      */
-    Page<ExamRecordInfo> getExamRecordList(ExamRecordQuery query);
+    Page<ExamRecordInfo> getExamRecordList(UserDataRules uds,ExamRecordQuery query);
 
     /**
      * 查询“考试明细”列表(分页)
@@ -38,7 +39,7 @@ public interface ExamRecordService {
      * @param query
      * @return
      */
-    Page<ExamRecordInfo> getExamRecordDetailListForPage(ExamRecordQuery query);
+    Page<ExamRecordInfo> getExamRecordDetailListForPage(UserDataRules uds,ExamRecordQuery query);
 
     /**
      * 查询“监考待审”列表(分页)
@@ -46,15 +47,15 @@ public interface ExamRecordService {
      * @param query
      * @return
      */
-    Page<ExamRecordInfo> getExamRecordWaitingAuditList(ExamRecordQuery query);
+    Page<ExamRecordInfo> getExamRecordWaitingAuditList(UserDataRules uds,ExamRecordQuery query);
 
-    /**
-     * 查询“监考待审”列表(分页)
-     *
-     * @param query
-     * @return
-     */
-    List<ExamRecordInfo> getExamRecordWaitingAudit(ExamRecordQuery query);
+//    /**
+//     * 查询“监考待审”列表(分页)
+//     *
+//     * @param query
+//     * @return
+//     */
+//    List<ExamRecordInfo> getExamRecordWaitingAudit(ExamRecordQuery query);
 
     /**
      * Description 查询“监考待审”下一条记录
@@ -64,7 +65,7 @@ public interface ExamRecordService {
      * @param next
      * @return java.lang.Long
      */
-    Long getExamRecordWaitingAuditNextId(ExamRecordQuery query, Long examRecordDataId, String next);
+    Long getExamRecordWaitingAuditNextId(UserDataRules uds,ExamRecordQuery query, Long examRecordDataId, String next);
 
     /**
      * 获取正在考试的考生ID列表
@@ -88,15 +89,15 @@ public interface ExamRecordService {
      * @param query
      * @return
      */
-    Long existsWarnExamRecordDetail(ExamRecordQuery query);
+    Long existsWarnExamRecordDetail(UserDataRules uds,ExamRecordQuery query);
 
-    /**
-     * 根据条件查询 “考试明细”
-     *
-     * @param newQuery
-     * @return
-     */
-    List<ExamRecordInfo> getExamRecordDetailList(ExamRecordQuery newQuery);
+//    /**
+//     * 根据条件查询 “考试明细”
+//     *
+//     * @param newQuery
+//     * @return
+//     */
+//    List<ExamRecordInfo> getExamRecordDetailList(ExamRecordQuery newQuery);
 
     /**
      * 根据考试id和课程代码获取学生作答成绩
@@ -116,8 +117,8 @@ public interface ExamRecordService {
      */
     List<ExamStudentEffectiveScoreInfo> getExamStudentEffectiveScoreList(String markingType, List<Long> examStudentIdList);
 
-    List<ExamRecordInfo> getExamRecordDetailListForAsync(ExamRecordQuery query);
-
     void refreshCaptureStatistic(Long examId);
 
+	List<ExamRecordInfo> getExamRecordDetailListForAsync(UserDataRules uds, ExamRecordQuery query);
+
 }

+ 4 - 3
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamScoreService.java

@@ -8,6 +8,7 @@
 package cn.com.qmth.examcloud.core.oe.admin.service;
 
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ObjectiveScoreInfo;
@@ -23,9 +24,9 @@ import java.util.List;
  */
 public interface ExamScoreService {
 
-    Page<ExamScoreInfo> getExamScoreList(ExamScoreQuery query);
+    Page<ExamScoreInfo> getExamScoreList(UserDataRules uds,ExamScoreQuery query);
 
-    List<ExamScoreInfo> exportExamScoreList(ExamScoreQuery query);
+//    List<ExamScoreInfo> exportExamScoreList(ExamScoreQuery query);
 
     /**
      * 获取所有的考试分数集合
@@ -54,6 +55,6 @@ public interface ExamScoreService {
      */
     public List<ObjectiveScoreInfo> queryObjectiveScoreList(Long examStudentId);
 
-    List<ExamScoreInfo> exportExamScoreListForAsync(ExamScoreQuery query);
+    List<ExamScoreInfo> exportExamScoreListForAsync(UserDataRules uds,ExamScoreQuery query);
 
 }

+ 11 - 7
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExamStudentService.java

@@ -8,7 +8,9 @@
 package cn.com.qmth.examcloud.core.oe.admin.service;
 
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.OnHandExamInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.*;
 import org.springframework.data.domain.Page;
 
@@ -48,22 +50,21 @@ public interface ExamStudentService {
 
     /**
      * 查询考生列表(分页)
+     * @param uds 
      *
      * @param query
      * @return
      */
-    Page<ExamStudentInfo> getExamStudentListPage(ExamStudentQuery query);
+    Page<ExamStudentInfo> getExamStudentListPage(UserDataRules uds, ExamStudentQuery query);
 
 
-    public List<ExamStudentInfo> getExamStudentInfoList(ExamStudentQuery query);
-
     /**
      * 查询重考考生列表(分页)
      *
      * @param query
      * @return
      */
-    Page<ExamStudentInfo> getReExamineStudentList(ExamStudentQuery query);
+    Page<ExamStudentInfo> getReExamineStudentList(UserDataRules uds,ExamStudentQuery query);
 
     /**
      * 考试概况-按考试完成进度统计
@@ -81,7 +82,7 @@ public interface ExamStudentService {
      * @param examStageId
      * @return
      */
-    List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(Long examId, Long examStageId, Long orgId);
+    List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(UserDataRule ud,Long examId, Long examStageId, Long orgId);
 
     /**
      * 获取考生信息
@@ -123,7 +124,7 @@ public interface ExamStudentService {
      * @param examId
      * @param courseId
      */
-    public List<CourseProgressInfo> queryCourseProgressInfos(Long examId, Long examStageId, Long courseId, String orderColumn);
+    public List<CourseProgressInfo> queryCourseProgressInfos(UserDataRule ud,Long examId, Long examStageId, Long courseId, String orderColumn);
 
     /**
      * @return java.util.List<cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentInfo>
@@ -143,8 +144,11 @@ public interface ExamStudentService {
      */
     public List<OnHandExamInfo> queryOnlineExamList(Long studentId, ExamType examType);
 
-    List<ExamStudentInfo> getExamStudentInfoListForAsync(ExamStudentQuery query);
 
     List<OnHandExamInfo> queryOnlineExamEndList(Long userId, ExamType online);
 
+	List<ExamStudentInfo> getExamStudentInfoList(UserDataRules uds, ExamStudentQuery query);
+
+	List<ExamStudentInfo> getExamStudentInfoListForAsync(UserDataRules uds, ExamStudentQuery query);
+
 }

+ 2 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/ExportTaskService.java

@@ -37,5 +37,7 @@ public interface ExportTaskService {
     void startExportTask(Long taskId);
 
     void endExportTask(Long taskId);
+    
+    ExportTaskEntity findById(Long taskId);
 
 }

+ 6 - 10
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/OfflineExamService.java

@@ -1,8 +1,8 @@
 package cn.com.qmth.examcloud.core.oe.admin.service;
 
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.OfflineExamCourseInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.uploadfile.FileInfo;
 
-import java.io.File;
 import java.util.List;
 
 
@@ -25,9 +25,7 @@ public interface OfflineExamService {
     /**
      * 开始离线考试
      *
-     * @param token
      * @param examStudentId
-     * @param studentId
      * @return
      */
     void startOfflineExam(Long examStudentId);
@@ -36,18 +34,16 @@ public interface OfflineExamService {
      * 上传作答
      *
      * @param examRecordDataId
-     * @param tempFile
-     * @param fileType
+     * @param fileInfo
      */
-    void submitPaper(Long examRecordDataId, File tempFile, String fileType) throws Exception;
+    void submitPaper(Long examRecordDataId, FileInfo fileInfo, Long userId);
 
     /**
      * 上传作答
      *
-     * @param examRecordDataId 考试记录id
-     * @param tempFiles        文件集合
-     * @param fileType         文件类型
+     * @param examRecordDataId
+     * @param fileInfoList
      */
-    void batchSubmitPaper(Long examRecordDataId, List<File> tempFiles, String fileType) throws Exception;
+    void batchSubmitPaper(Long examRecordDataId, List<FileInfo> fileInfoList, String fileType, Long userId);
 
 }

+ 33 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/UserDataRules.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
+
+public class UserDataRules {
+	
+	private UserDataRule orgRule;
+	
+	private UserDataRule courseRule;
+	
+	
+	public UserDataRules() {
+		super();
+	}
+	public UserDataRules(UserDataRule orgRule, UserDataRule courseRule) {
+		super();
+		this.orgRule = orgRule;
+		this.courseRule = courseRule;
+	}
+	public UserDataRule getOrgRule() {
+		return orgRule;
+	}
+	public void setOrgRule(UserDataRule orgRule) {
+		this.orgRule = orgRule;
+	}
+	public UserDataRule getCourseRule() {
+		return courseRule;
+	}
+	public void setCourseRule(UserDataRule courseRule) {
+		this.courseRule = courseRule;
+	}
+
+}

+ 11 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/examrecord/ExamRecordQuery.java

@@ -114,6 +114,9 @@ public class ExamRecordQuery implements JsonSerializable {
 
     @ApiModelProperty("Ip")
     private String ip;
+    
+    @ApiModelProperty("虚拟设备名")
+    private String virtualName;
 
     @ApiModelProperty("审核人")
     private String auditUserName;
@@ -413,5 +416,13 @@ public class ExamRecordQuery implements JsonSerializable {
 		this.switchScreenCountEnd = switchScreenCountEnd;
 	}
 
+	public String getVirtualName() {
+		return virtualName;
+	}
+
+	public void setVirtualName(String virtualName) {
+		this.virtualName = virtualName;
+	}
+
     
 }

+ 129 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/bean/uploadfile/FileInfo.java

@@ -0,0 +1,129 @@
+package cn.com.qmth.examcloud.core.oe.admin.service.bean.uploadfile;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+
+/**
+ * 文件信息
+ */
+public class FileInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = 6780320204234657366L;
+
+    /**
+     * 文件内容
+     */
+    private byte[] fileBytes;
+
+    /**
+     * 文件大小
+     */
+    private long fileSize;
+
+    /**
+     * 原始文件名称
+     */
+    private String originalFileName;
+
+    /**
+     * 文件名称
+     */
+    private String fileName;
+
+    /**
+     * 文件路径
+     */
+    private String filePath;
+
+    /**
+     * 文件访问地址
+     */
+    private String fileUrl;
+
+    /**
+     * 文件类型
+     */
+    private String fileType;
+
+    /**
+     * 文件后缀名
+     */
+    private String fileSuffix;
+
+    /**
+     * 文件MD5
+     */
+    private String md5;
+
+    public byte[] getFileBytes() {
+        return fileBytes;
+    }
+
+    public void setFileBytes(byte[] fileBytes) {
+        this.fileBytes = fileBytes;
+    }
+
+    public long getFileSize() {
+        return fileSize;
+    }
+
+    public void setFileSize(long fileSize) {
+        this.fileSize = fileSize;
+    }
+
+    public String getOriginalFileName() {
+        return originalFileName;
+    }
+
+    public void setOriginalFileName(String originalFileName) {
+        this.originalFileName = originalFileName;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public String getFileUrl() {
+        return fileUrl;
+    }
+
+    public void setFileUrl(String fileUrl) {
+        this.fileUrl = fileUrl;
+    }
+
+    public String getFileType() {
+        return fileType;
+    }
+
+    public void setFileType(String fileType) {
+        this.fileType = fileType;
+    }
+
+    public String getFileSuffix() {
+        return fileSuffix;
+    }
+
+    public void setFileSuffix(String fileSuffix) {
+        this.fileSuffix = fileSuffix;
+    }
+
+    public String getMd5() {
+        return md5;
+    }
+
+    public void setMd5(String md5) {
+        this.md5 = md5;
+    }
+
+}

+ 84 - 19
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/AsyncExportServiceImpl.java

@@ -5,14 +5,45 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
+import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.DateUtil;
 import cn.com.qmth.examcloud.commons.util.JsonMapper;
+import cn.com.qmth.examcloud.core.basic.api.UserCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.UserBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetUserReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetUserResp;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.RoleUtil;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExportTaskEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskStatus;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExportTaskType;
-import cn.com.qmth.examcloud.core.oe.admin.service.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.AsyncExportService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamAuditService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExportTaskService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditQuery;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInfo;
@@ -30,23 +61,11 @@ import cn.com.qmth.examcloud.support.cache.bean.ExamStageCacheBean;
 import cn.com.qmth.examcloud.support.excel.ExcelExportUtil;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.support.helper.IdentityNumberHelper;
+import cn.com.qmth.examcloud.web.bootstrap.PropertyHolder;
 import cn.com.qmth.examcloud.web.config.SystemProperties;
 import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
 import cn.com.qmth.examcloud.web.filestorage.YunPathInfo;
-import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.io.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-import java.util.UUID;
+import cn.com.qmth.examcloud.web.security.ResourceManager;
 
 /**
  * 异步导出相关接口
@@ -75,6 +94,12 @@ public class AsyncExportServiceImpl implements AsyncExportService {
 
     @Autowired
     private ExamAuditService examAuditService;
+    
+    @Autowired
+    private  UserCloudService userCloudService;
+    
+    @Autowired
+    private ResourceManager resourceManager;
 
     @Override
     public void exportExamScheduling(String jsonParams, User user) {
@@ -99,7 +124,9 @@ public class AsyncExportServiceImpl implements AsyncExportService {
         List<ExamStudentExcel> examRecords;
         List<ExamStudentInfo> examStudentInfoList;
         try {
-            examStudentInfoList = examStudentService.getExamStudentInfoListForAsync(req);
+        	ExportTaskEntity task=exportTaskService.findById(taskId);
+        	UserDataRules uds=new UserDataRules(getUserDataRule(task.getCreator(),DataRuleType.ORG), getUserDataRule(task.getCreator(),DataRuleType.COURSE));
+            examStudentInfoList = examStudentService.getExamStudentInfoListForAsync(uds,req);
             if (CollectionUtils.isEmpty(examStudentInfoList)) {
                 exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
                 return;
@@ -194,7 +221,10 @@ public class AsyncExportServiceImpl implements AsyncExportService {
     public void asyncExportExamRecordDetails(Long taskId, ExamRecordQuery req) {
         List<ExamRecordInfo> examRecords;
         try {
-            examRecords = examRecordService.getExamRecordDetailListForAsync(req);
+        	ExportTaskEntity task=exportTaskService.findById(taskId);
+        	UserDataRules uds=new UserDataRules(getUserDataRule(task.getCreator(),DataRuleType.ORG), getUserDataRule(task.getCreator(),DataRuleType.COURSE));
+            
+            examRecords = examRecordService.getExamRecordDetailListForAsync(uds,req);
 
             if (CollectionUtils.isEmpty(examRecords)) {
                 exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
@@ -269,7 +299,10 @@ public class AsyncExportServiceImpl implements AsyncExportService {
     public void asyncExportExamScoreStatistics(Long taskId, ExamScoreQuery req) {
         List<ExamScoreInfo> examScores;
         try {
-            examScores = examScoreService.exportExamScoreListForAsync(req);
+        	ExportTaskEntity task=exportTaskService.findById(taskId);
+        	UserDataRules uds=new UserDataRules(getUserDataRule(task.getCreator(),DataRuleType.ORG), getUserDataRule(task.getCreator(),DataRuleType.COURSE));
+            
+            examScores = examScoreService.exportExamScoreListForAsync(uds,req);
             if (CollectionUtils.isEmpty(examScores)) {
                 exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
                 return;
@@ -321,7 +354,10 @@ public class AsyncExportServiceImpl implements AsyncExportService {
     public void asyncExportExamAuditList(Long taskId, ExamAuditQuery req) {
         List<ExamAuditInfo> auditInfos;
         try {
-            auditInfos = examAuditService.getExamAudit(req);
+        	ExportTaskEntity task=exportTaskService.findById(taskId);
+        	UserDataRules uds=new UserDataRules(getUserDataRule(task.getCreator(),DataRuleType.ORG), getUserDataRule(task.getCreator(),DataRuleType.COURSE));
+            
+            auditInfos = examAuditService.getExamAudit(uds,req);
 
             if (CollectionUtils.isEmpty(auditInfos)) {
                 exportTaskService.updateExportTaskStatus(taskId, ExportTaskStatus.ERROR, "当前条件暂无数据,任务终止");
@@ -402,5 +438,34 @@ public class AsyncExportServiceImpl implements AsyncExportService {
         final String pattern = "yyyyMM";
         return new SimpleDateFormat(pattern).format(new Date());
     }
+    
+    private UserDataRule getUserDataRule(Long userId,DataRuleType dataRuleType) {
+    	UserDataRule userDataRule= new UserDataRule();
+        userDataRule.setGlobalStatus(true);
+        userDataRule.setRefIds(new HashSet<>());
+    	boolean isEnable = PropertyHolder.getBoolean("examcloud.data.rule.enable", false);
+        if (!isEnable) {
+            // 未启用数据权限
+            return userDataRule;
+        }
+
+        GetUserReq getUserReq = new GetUserReq();
+        getUserReq.setUserId(userId);
+        GetUserResp getUserResp = userCloudService.getUser(getUserReq);
+        UserBean userBean = getUserResp.getUserBean();
+        if (userBean == null) {
+            throw new StatusException("未找到用户");
+        }
+
+        boolean isAdmimUser = RoleUtil.hasAnyRoles(userBean, RoleMeta.SUPER_ADMIN, RoleMeta.ORG_ADMIN);
+
+        if (isAdmimUser) {
+            // “超管、机构管理员、学生” 暂例外
+        	return userDataRule;
+        } else {
+            userDataRule = resourceManager.loadUserDataRule(userId, dataRuleType);
+        }
+        return userDataRule;
+    }
 
 }

+ 121 - 41
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamAuditServiceImpl.java

@@ -7,6 +7,31 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
+import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditMapper.EXAM_RECORD_DATA_ID;
+
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+
+import org.apache.commons.lang3.StringUtils;
+import org.hibernate.query.NativeQuery;
+import org.hibernate.transform.Transformers;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import com.google.common.collect.Lists;
+
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.DateUtil;
@@ -15,11 +40,43 @@ import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SqlWrapper;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.CommonUtil;
-import cn.com.qmth.examcloud.core.oe.admin.dao.*;
-import cn.com.qmth.examcloud.core.oe.admin.dao.entity.*;
-import cn.com.qmth.examcloud.core.oe.admin.dao.enums.*;
-import cn.com.qmth.examcloud.core.oe.admin.service.*;
-import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.*;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamAuditRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamFaceLiveVerifyRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamFaceLivenessVerifyRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordForMarkingRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentFinalScoreRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.FaceBiopsyItemRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.IllegallyTypeRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamAuditEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamFaceLiveVerifyEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamFaceLivenessVerifyEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordForMarkingEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentFinalScoreEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.FaceBiopsyItemEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.entity.IllegallyTypeEntity;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.AuditStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.CourseLevel;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.DisciplineType;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.FaceVerifyResult;
+import cn.com.qmth.examcloud.core.oe.admin.dao.enums.SelectType;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamAuditService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreObtainQueueService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushQueueService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentFinalScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
+import cn.com.qmth.examcloud.core.oe.admin.service.IllegallyTypeService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditEntityConvert;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditMapper;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditQuery;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.RedoAuditInfo;
 import cn.com.qmth.examcloud.marking.api.MarkWorkCloudService;
 import cn.com.qmth.examcloud.marking.api.request.AppendMarkWorkPaperReq;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
@@ -31,29 +88,6 @@ import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
-import com.google.common.collect.Lists;
-
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.hibernate.query.NativeQuery;
-import org.hibernate.transform.Transformers;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.Pageable;
-import org.springframework.data.jpa.domain.Specification;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
-import java.math.BigInteger;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-import static cn.com.qmth.examcloud.core.oe.admin.service.bean.examaudit.ExamAuditMapper.EXAM_RECORD_DATA_ID;
 
 /**
  * 考试记录审核相关接口
@@ -103,6 +137,9 @@ public class ExamAuditServiceImpl implements ExamAuditService {
     @Autowired
     private FaceBiopsyItemRepo faceBiopsyItemRepo;
 
+    @Autowired
+    private ExamFaceLiveVerifyRepo examFaceLiveVerifyRepo;
+
     @Autowired
     private ExamStudentFinalScoreService examStudentFinalScoreService;
 
@@ -128,14 +165,17 @@ public class ExamAuditServiceImpl implements ExamAuditService {
     private static final String AUDIT_USER_NAME = "SYSTEM";
 
     @Override
-    public Page<ExamAuditInfo> getExamAuditList(ExamAuditQuery query) {
+    public Page<ExamAuditInfo> getExamAuditList(UserDataRules uds,ExamAuditQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return Page.empty();
+    	}
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
         //封装查询条件
         Pageable pageable = SpecUtils.buildPageable(query.getPageNo(), query.getPageSize());
 
         final String columns = ExamAuditMapper.defaultColumns();
-        SqlWrapper wrapper = getSqlWrapper(query);
+        SqlWrapper wrapper = getSqlWrapper(uds,query);
 
         long totalSize = 0;
         //查询总记录数
@@ -196,11 +236,11 @@ public class ExamAuditServiceImpl implements ExamAuditService {
     }
 
     @Override
-    public List<ExamAuditInfo> getExamAudit(ExamAuditQuery query) {
+    public List<ExamAuditInfo> getExamAudit(UserDataRules uds,ExamAuditQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
 
-        SqlWrapper wrapper = getSqlWrapper(query);
+        SqlWrapper wrapper = getSqlWrapper(uds,query);
 
         wrapper.groupBy("record.id ");
         //查询分页记录
@@ -258,7 +298,7 @@ public class ExamAuditServiceImpl implements ExamAuditService {
     }
 
 
-    private SqlWrapper getSqlWrapper(ExamAuditQuery query) {
+    private SqlWrapper getSqlWrapper(UserDataRules uds,ExamAuditQuery query) {
         final String columns = ExamAuditMapper.defaultColumns();
         SqlWrapper wrapper = new SqlWrapper()
                 .select(columns).from("ec_oe_exam_audit").as("audit")
@@ -324,6 +364,13 @@ public class ExamAuditServiceImpl implements ExamAuditService {
             wrapper.and().gte("audit.update_time", query.getAuditStartTime());
             wrapper.and().lte("audit.update_time", query.getAuditEndTime());
         }
+        
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+        	wrapper.and().in("record.org_id", uds.getOrgRule().getRefIds());
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+        	wrapper.and().in("record.course_id", uds.getCourseRule().getRefIds());
+        }
         return wrapper;
     }
 
@@ -520,7 +567,7 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         }
         ExamAuditEntity examAuditEntity = new ExamAuditEntity();
         examAuditEntity.setExamRecordDataId(realExamRecordDataId);
-        examAuditEntity.setDisciplineType(DisciplineType.ILLEGAL_CLIENT.name());
+        examAuditEntity.setDisciplineType(DisciplineType.ILLEGAL_DATA.name());
         examAuditEntity.setDisciplineDetail(null);
         examAuditEntity.setUserId(DisciplineType.ILLEGAL_DATA.name());
         examAuditEntity.setAuditUserName(AUDIT_USER_NAME);
@@ -546,13 +593,28 @@ public class ExamAuditServiceImpl implements ExamAuditService {
     //获取活检违纪详情
     private String getFaceVerifyDisciplineDetail(Long examRecordDataId, Long rootOrgId) {
         FaceBiopsyScheme faceBiopsyScheme = FaceBiopsyHelper.getFaceBiopsyScheme(rootOrgId);
+        if (FaceBiopsyScheme.FACE_CLIENT == faceBiopsyScheme) {
+            // C端活体检测方案
+            List<ExamFaceLiveVerifyEntity> entities = examFaceLiveVerifyRepo.findByExamRecordDataId(examRecordDataId);
+            if (CollectionUtils.isEmpty(entities)) {
+                return "未进行人脸活体检测";
+            }
 
-        //新活体检测方案从新表中取相关数据
-        if (faceBiopsyScheme == FaceBiopsyScheme.NEW) {
+            StringBuilder result = new StringBuilder();
+            for (ExamFaceLiveVerifyEntity entity : entities) {
+                result.append(CommonUtil.getDateStrWithSecond(entity.getCreationTime()))
+                        .append(":")
+                        .append(entity.getStatus().getDescription())
+                        .append("&&");
+            }
+
+            return result.toString();
+        } else if (faceBiopsyScheme == FaceBiopsyScheme.FACE_MOTION) {
+            // Electron Client 自研活体检测方案
             List<FaceBiopsyItemEntity> faceBiopsyItems = faceBiopsyItemRepo.findByExamRecordDataIdOrderByIdAsc(examRecordDataId);
+
             //从未调过人脸检测接口,或者只调过接口但是未保存过结果(即未完成活检),则提示未进行人脸活体检测
-            if (faceBiopsyItems == null ||
-                    (faceBiopsyItems.size() == 1 && !faceBiopsyItems.get(0).getCompleted())) {
+            if (faceBiopsyItems == null || (faceBiopsyItems.size() == 1 && !faceBiopsyItems.get(0).getCompleted())) {
                 return "未进行人脸活体检测";
             }
 
@@ -573,9 +635,8 @@ public class ExamAuditServiceImpl implements ExamAuditService {
                 sbBuffer.append("&&");
             }
             return sbBuffer.toString();
-        }
-        //旧活体检测方案从旧表中取相关数据
-        else {
+        } else {
+            // FaceID活体检测方案
             List<ExamFaceLivenessVerifyEntity> faceVerifies =
                     examFaceLivenessVerifyRepo.findByExamRecordDataIdOrderById(examRecordDataId);
             if (faceVerifies == null || faceVerifies.size() == 0) {
@@ -685,4 +746,23 @@ public class ExamAuditServiceImpl implements ExamAuditService {
         examScoreObtainQueueService.sendObtainScoreNotify(examRecordData.getRootOrgId());
     }
 
+	@Override
+	public void saveExceedMaxSwitchScreenCount(Long realExamRecordDataId) {
+		ExamAuditEntity examAudit = examAuditRepo.findByExamRecordDataId(realExamRecordDataId);
+        if (examAudit != null) {
+            return;
+        }
+        ExamAuditEntity examAuditEntity = new ExamAuditEntity();
+        examAuditEntity.setExamRecordDataId(realExamRecordDataId);
+        examAuditEntity.setDisciplineType(DisciplineType.EXCEED_MAX_SWITCH_SCREEN_COUNT.name());
+        examAuditEntity.setDisciplineDetail(null);
+        examAuditEntity.setUserId(DisciplineType.EXCEED_MAX_SWITCH_SCREEN_COUNT.name());
+        examAuditEntity.setAuditUserName(AUDIT_USER_NAME);
+        examAuditEntity.setStatus(AuditStatus.UN_PASS);
+        examAuditRepo.save(examAuditEntity);
+
+        // 修改考试记录为违纪、异常、已审
+        this.disciplineExamRecordData(realExamRecordDataId);
+	}
+
 }

+ 40 - 57
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamCaptureServiceImpl.java

@@ -7,7 +7,7 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
-import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamCaptureCameraInfoRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamCaptureRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
@@ -29,7 +29,6 @@ import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
-import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.slf4j.Logger;
@@ -53,7 +52,10 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
 
     @Autowired
     private ExamCaptureRepo examCaptureRepo;
-
+    
+    @Autowired
+    private ExamCaptureCameraInfoRepo examCaptureCameraInfoRepo;
+    
     @Autowired
     private ExamAuditService examAuditService;
 
@@ -167,31 +169,12 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
     @Override
     public String getVirtualCameraNames(Long examRecordDataId) {
         Set<String> virtualCameraNames = new HashSet<String>();
-        List<ExamCaptureEntity> examCaptureList = examCaptureRepo.findByExamRecordDataId(examRecordDataId);
+        List<ExamCaptureCameraInfoEntity> examCaptureList = examCaptureCameraInfoRepo.findByExamRecordDataIdAndVirtualCamera(examRecordDataId,true);
         if (examCaptureList != null && examCaptureList.size() > 0) {
-            for (ExamCaptureEntity examCapture : examCaptureList) {
-                String cameraInfos = examCapture.getCameraInfos();
+            for (ExamCaptureCameraInfoEntity examCapture : examCaptureList) {
+                String cameraInfos = examCapture.getName();
                 if (StringUtils.isNotBlank(cameraInfos)) {
-                    JSONArray jsonArray;
-                    try {
-                        jsonArray = new JSONArray(cameraInfos);
-                        for (int i = 0; i < jsonArray.length(); i++) {
-                            try {
-                                JSONObject jsonObject = (JSONObject) jsonArray.get(i);
-                                if (jsonObject.isNull("pid") || StringUtils.isBlank(jsonObject.getString("pid"))) {
-                                    virtualCameraNames.add(jsonObject.getString("name"));
-                                }
-                            } catch (JSONException e) {
-                                //主要针对json数组最后的空对象处理,不影响业务
-                                LOG.error("抓拍照片格式不正确", e);
-                                continue;
-                            }
-                        }
-                    } catch (JSONException e) {
-                        LOG.error("获取虚拟摄像头名称失败", e);
-                        throw new StatusException("ExamCaptureService-001", "获取虚拟摄像头名称失败");
-                    }
-
+                	virtualCameraNames.add(cameraInfos);
                 }
             }
         }
@@ -229,40 +212,40 @@ public class ExamCaptureServiceImpl implements ExamCaptureService {
         String faceLivenessResult = entity.getFacelivenessResult();
         info.setIsFacelivenessPass(isFaceLivenessPass(faceLivenessResult));
 
-        String cameraInfos = entity.getCameraInfos();
-        info.setVirtualCameraNames(getVirtualCameraNames(cameraInfos));
+//        String cameraInfos = entity.getCameraInfos();
+//        info.setVirtualCameraNames(getVirtualCameraNames(cameraInfos));
         return info;
     }
 
-    private String getVirtualCameraNames(String cameraInfos) {
-        StringBuffer sb = new StringBuffer();
-        if (StringUtils.isNotBlank(cameraInfos)) {
-            JSONArray jsonArray;
-            try {
-                jsonArray = new JSONArray(cameraInfos);
-                for (int i = 0; i < jsonArray.length(); i++) {
-                    try {
-                        JSONObject jsonObject = (JSONObject) jsonArray.get(i);
-                        if (jsonObject.isNull("pid") || StringUtils.isBlank(jsonObject.getString("pid"))) {
-                            sb.append(jsonObject.getString("name") + ",");
-                        }
-                    } catch (JSONException e) {
-                        //主要针对json数组最后的空对象处理,不影响业务eg:"[   {      \"name\" : \"Camera\",      \"pid\" : \"\",      \"vid\" : \"\"   },]";
-                        LOG.error("抓拍照片格式不正确", e);
-                        continue;
-                    }
-                }
-            } catch (JSONException e) {
-                LOG.error("获取虚拟摄像头名称失败", e);
-                throw new StatusException("ExamCaptureService-002", "获取虚拟摄像头名称失败");
-            }
-
-            if (sb.lastIndexOf(",") > -1) {
-                return sb.substring(0, sb.lastIndexOf(","));
-            }
-        }
-        return sb.toString();
-    }
+//    private String getVirtualCameraNames(String cameraInfos) {
+//        StringBuffer sb = new StringBuffer();
+//        if (StringUtils.isNotBlank(cameraInfos)) {
+//            JSONArray jsonArray;
+//            try {
+//                jsonArray = new JSONArray(cameraInfos);
+//                for (int i = 0; i < jsonArray.length(); i++) {
+//                    try {
+//                        JSONObject jsonObject = (JSONObject) jsonArray.get(i);
+//                        if (jsonObject.isNull("pid") || StringUtils.isBlank(jsonObject.getString("pid"))) {
+//                            sb.append(jsonObject.getString("name") + ",");
+//                        }
+//                    } catch (JSONException e) {
+//                        //主要针对json数组最后的空对象处理,不影响业务eg:"[   {      \"name\" : \"Camera\",      \"pid\" : \"\",      \"vid\" : \"\"   },]";
+//                        LOG.error("抓拍照片格式不正确", e);
+//                        continue;
+//                    }
+//                }
+//            } catch (JSONException e) {
+//                LOG.error("获取虚拟摄像头名称失败", e);
+//                throw new StatusException("ExamCaptureService-002", "获取虚拟摄像头名称失败");
+//            }
+//
+//            if (sb.lastIndexOf(",") > -1) {
+//                return sb.substring(0, sb.lastIndexOf(","));
+//            }
+//        }
+//        return sb.toString();
+//    }
 
     private boolean isFaceLivenessPass(String faceLivenessResult) {
         if (StringUtils.isNotBlank(faceLivenessResult)) {

+ 23 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamProcessRecordServiceImpl.java

@@ -5,11 +5,15 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.ExamProcessRecordRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamProcessRecordEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamProcessRecordService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamProcessRecordInfo;
+
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * @Description 考试过程记录
@@ -50,4 +54,23 @@ public class ExamProcessRecordServiceImpl implements ExamProcessRecordService {
         return resultList;
     }
 
+	@Override
+	public String getIps(Long examRecordDataId) {
+		Set<String> ips = new HashSet<String>();
+        List<ExamProcessRecordEntity> list = examProcessRecordRepo.findByExamRecordDataId(examRecordDataId);
+        if (list != null && list.size() > 0) {
+            for (ExamProcessRecordEntity ep : list) {
+                String ip = ep.getSourceIp();
+                if (StringUtils.isNotBlank(ip)) {
+                	ips.add(ip);
+                }
+            }
+        }
+        StringBuffer sb = new StringBuffer();
+        for (String ip : ips) {
+            sb.append(ip).append(";");
+        }
+        return sb.toString();
+	}
+
 }

+ 7 - 2
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataServiceImpl.java

@@ -69,8 +69,13 @@ public class ExamRecordDataServiceImpl implements ExamRecordDataService {
 
     @Override
     public Long getGetAwaitingAuditCount(Long rootOrgId, Long examId) {
-        Long count = jdbcTemplate.queryForObject("select count(1) from ec_oe_exam_record_data t where t.is_warn=1 and t.is_audit = 0 and t.root_org_id=" + rootOrgId + " and t.exam_id=" + examId, Long.class);
-        return count;
+        StringBuilder sql = new StringBuilder()
+                .append(" select count(1) from ec_oe_exam_record_data t")
+                .append(" where t.exam_id = ").append(examId)
+                .append(" and t.exam_record_status != 'EXAM_INVALID'")
+                .append(" and t.is_warn=1 and t.is_audit = 0")
+                .append(" and t.root_org_id = ").append(rootOrgId);
+        return jdbcTemplate.queryForObject(sql.toString(), Long.class);
     }
 
     @Override

+ 6 - 3
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordDataSyncServiceImpl.java

@@ -1,5 +1,6 @@
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataSyncRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamRecordDataSyncEntity;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordDataSyncService;
@@ -12,13 +13,15 @@ public class ExamRecordDataSyncServiceImpl implements ExamRecordDataSyncService
     @Autowired
     private ExamRecordDataSyncRepo examRecordDataSyncRepo;
 
-
     @Override
-    public Long getExamRecordDataIdByCacheId(Long cacheId) {
-        ExamRecordDataSyncEntity e = examRecordDataSyncRepo.findByCacheId(cacheId);
+    public Long getExamRecordDataIdByCacheId(Long tempExamRecordDataId) {
+        Check.isNull(tempExamRecordDataId, "tempExamRecordDataId不能为空");
+
+        ExamRecordDataSyncEntity e = examRecordDataSyncRepo.findByCacheId(tempExamRecordDataId);
         if (e != null) {
             return e.getDbId();
         }
+
         return null;
     }
 

+ 0 - 28
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordForMarkingServiceImpl.java

@@ -9,9 +9,7 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.enums.MarkingType;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordForMarkingService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordQuestionsService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordDataBean;
-import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
-import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import org.apache.commons.lang3.StringUtils;
@@ -38,9 +36,6 @@ public class ExamRecordForMarkingServiceImpl implements ExamRecordForMarkingServ
     @Autowired
     private ExamRecordForMarkingRepo examRecordForMarkingRepo;
 
-    @Autowired
-    private ExamCloudService examCloudService;
-
     @Autowired
     private ExamRecordDataRepo examRecordDataRepo;
 
@@ -248,29 +243,6 @@ public class ExamRecordForMarkingServiceImpl implements ExamRecordForMarkingServ
         });
     }
 
-    @Override
-    public void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData, String offlineFileName, String fileUrl) {
-        ExamRecordForMarkingEntity examRecordForMarking = examRecordForMarkingRepo.findByExamRecordDataId(examRecordData.getId());
-        if (examRecordForMarking == null) {
-            examRecordForMarking = new ExamRecordForMarkingEntity();
-            examRecordForMarking.setCreationTime(new Date());
-        } else {
-            //将原文件删掉
-            FileStorageUtil.deleteFile(fileUrl);
-        }
-
-        examRecordForMarking.setExamId(examRecordData.getExamId());
-        examRecordForMarking.setExamRecordDataId(examRecordData.getId());
-        examRecordForMarking.setExamStudentId(examRecordData.getExamStudentId());
-        examRecordForMarking.setCourseId(examRecordData.getCourseId());
-        examRecordForMarking.setObjectiveScore(0D);
-        examRecordForMarking.setSubjectiveAnswerLength(0);
-        examRecordForMarking.setBasePaperId(examRecordData.getBasePaperId());
-        examRecordForMarking.setPaperType(examRecordData.getPaperType());
-        examRecordForMarking.setUpdateTime(new Date());
-        examRecordForMarkingRepo.save(examRecordForMarking);
-    }
-
     @Override
     public void saveOffLineExamRecordForMarking(ExamRecordDataBean examRecordData) {
         ExamRecordForMarkingEntity examRecordForMarking = examRecordForMarkingRepo.findByExamRecordDataId(examRecordData.getId());

+ 3 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordPaperStructServiceImpl.java

@@ -2,6 +2,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.NewQuestionType;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Sentence;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
@@ -47,6 +48,8 @@ public class ExamRecordPaperStructServiceImpl implements ExamRecordPaperStructSe
 
     @Override
     public ExamRecordPaperStructEntity getExamRecordPaperStruct(Long examRecordDataId) {
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+
         ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
         if (examRecordDataEntity == null) {
             throw new StatusException("400404", "未找到考试记录数据");

+ 2 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordQuestionsServiceImpl.java

@@ -2,6 +2,7 @@ package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.QuestionTypeUtil;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
@@ -190,6 +191,7 @@ public class ExamRecordQuestionsServiceImpl implements ExamRecordQuestionsServic
 
     @Override
     public ExamRecordQuestionsEntity getExamRecordQuestionsAndFixExamRecordDataIfNecessary(Long examRecordDataId) {
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
         ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo,
                 examRecordDataId, ExamRecordDataEntity.class);
         return getExamRecordQuestionsAndFixExamRecordDataIfNecessary(examRecordData);

+ 232 - 198
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamRecordServiceImpl.java

@@ -18,7 +18,9 @@ import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SqlWrapper;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.BatchSetDataUtil;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.DateUtils;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamCaptureCameraInfoRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamCaptureRepo;
+import cn.com.qmth.examcloud.core.oe.admin.dao.ExamProcessRecordRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordQuestionsRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
@@ -27,8 +29,8 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.enums.*;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
 import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
-import cn.com.qmth.examcloud.core.oe.admin.service.LocalCacheService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.ExamStudentEffectiveScoreInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordEntityConvert;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordQuery;
@@ -45,11 +47,6 @@ import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.support.helper.FaceBiopsyHelper;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
@@ -78,8 +75,6 @@ import java.util.stream.Collectors;
 @Service
 public class ExamRecordServiceImpl implements ExamRecordService {
 
-    private static final Logger LOG = LoggerFactory.getLogger(ExamRecordServiceImpl.class);
-
     @Autowired
     private ExamScoreRepo examScoreRepo;
 
@@ -101,14 +96,20 @@ public class ExamRecordServiceImpl implements ExamRecordService {
     @Autowired
     private ExamRecordEntityConvert examRecordEntityConvert;
 
-    @Autowired
-    private LocalCacheService localCacheService;
+//    @Autowired
+//    private LocalCacheService localCacheService;
 
     @Autowired
     private StudentCloudService studentCloudService;
 
     @Autowired
     private ExamCaptureRepo examCaptureRepo;
+    
+    @Autowired
+    private ExamCaptureCameraInfoRepo examCaptureCameraInfoRepo;
+    
+    @Autowired
+    private ExamProcessRecordRepo examProcessRecordRepo;
 
 
     private static final String EXAM_RECORD_EXPORT_SQL = "select  record_data.id," +
@@ -152,7 +153,6 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             "record_data.switch_screen_count switchScreenCount," +
             "record_data.exam_stage_id examStageId," +
             "audit.audit_user_name audit_user_name," +
-            "GROUP_CONCAT(DISTINCT process.source_ip) ip," +
             "eoes.objective_score objectiveTotalScore," +
             "eoes.subjective_score subjectiveTotalScore," +
             "eoes.total_score paperTotalScore," +
@@ -164,12 +164,13 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             " LEFT JOIN ec_oe_exam_audit AS audit ON record_data.id = audit.exam_record_data_id " +
             " LEFT JOIN ec_oe_exam_score eoes ON  record_data.id = eoes.exam_record_data_id " +
             " LEFT JOIN ec_oe_exam_student eoest ON  record_data.exam_student_id = eoest.exam_student_id " +
+            " left join ec_oe_exam_capture_camera_info camera on record_data.id=camera.exam_record_data_id "+
             " where 1=1";
 
     /**
      * ec_oe_exam_record_data  查询sql
      */
-    private static final String EXAM_RECORD_SQL = "select  record_data.id," +
+    private static final String EXAM_RECORD_HEAD_SQL = "select  record_data.id," +
             "record_data.exam_record_status," +
             "record_data.start_time," +
             "record_data.end_time," +
@@ -209,11 +210,12 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             "record_data.info_collector info_collector," +
             "record_data.switch_screen_count switchScreenCount," +
             "record_data.exam_stage_id examStageId," +
-            "audit.audit_user_name audit_user_name," +
-            "GROUP_CONCAT(DISTINCT process.source_ip) ip" +
-            " from ec_oe_exam_record_data record_data " +
+            "audit.audit_user_name audit_user_name ";
+            
+    private static final String EXAM_RECORD_FROM_SQL = " from ec_oe_exam_record_data record_data " +
             " LEFT JOIN ec_oe_exam_process_record AS process ON record_data.id = process.exam_record_data_id " +
             " LEFT JOIN ec_oe_exam_audit AS audit ON record_data.id = audit.exam_record_data_id " +
+            " left join ec_oe_exam_capture_camera_info camera on record_data.id=camera.exam_record_data_id "+
             " where 1=1";
 
     /**
@@ -231,61 +233,64 @@ public class ExamRecordServiceImpl implements ExamRecordService {
     private ExamRecordQuestionsRepo examRecordQuestionsRepo;
 
     @Override
-    public Page<ExamRecordInfo> getExamRecordDetailListForPage(ExamRecordQuery query) {
+    public Page<ExamRecordInfo> getExamRecordDetailListForPage(UserDataRules uds,ExamRecordQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
         query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
         query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
-        return this.loadData(_getExamRecordDetailListForPage(query), query.getExamId());
+        return this.loadData(_getExamRecordDetailListForPage(uds,query), query.getExamId());
     }
 
     @Override
-    public Page<ExamRecordInfo> getExamRecordWaitingAuditList(ExamRecordQuery query) {
+    public Page<ExamRecordInfo> getExamRecordWaitingAuditList(UserDataRules uds,ExamRecordQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
         //默认条件
         query.setIsAudit(false);
         query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
         query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
-        return this.getExamRecordList(query);
+        return this.getExamRecordList(uds,query);
     }
 
-    @Override
-    public List<ExamRecordInfo> getExamRecordWaitingAudit(ExamRecordQuery query) {
-        Check.isNull(query, "查询参数不能为空!");
-        //默认条件
-        query.setIsAudit(false);
-        query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
-        query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
-
-        //查询条件
-        StringBuilder sqlBuilder = new StringBuilder();
-        //待审核(按原先的sql,多了一个ip字段)
-        sqlBuilder.append(EXAM_RECORD_SQL);
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
-        if (query.getIsWarn() != null) {
-            //只查有异常未审核
-            if (query.getIsWarn()) {
-                sqlBuilder.append(" and record_data.is_warn  = 1 and record_data.is_audit = 0 ");
-            } else {
-                sqlBuilder.append(" and record_data.is_warn  = 0 ");
-            }
-        } else {
-            sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 0))");
-        }
-        sqlBuilder.append(" and record_data.is_illegality = 0");
-        //查询分页记录
-        sqlBuilder.append(" group by record_data.id ");
-
-        sqlBuilder.append(" order by record_data.id desc");
-        List<ExamRecordDataEntity> entities = jdbcTemplate.query(sqlBuilder.toString(), (rs, rowNum) -> getExamRecordDataEntityByResultSet(rs));
-
-        return examRecordEntityConvert.ofList(entities);
-    }
+//    @Override
+//    public List<ExamRecordInfo> getExamRecordWaitingAudit(ExamRecordQuery query) {
+//        Check.isNull(query, "查询参数不能为空!");
+//        //默认条件
+//        query.setIsAudit(false);
+//        query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
+//        query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
+//
+//        //查询条件
+//        StringBuilder sqlBuilder = new StringBuilder();
+//        //待审核(按原先的sql,多了一个ip字段)
+//        sqlBuilder.append(EXAM_RECORD_SQL);
+//        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+//        if (query.getIsWarn() != null) {
+//            //只查有异常未审核
+//            if (query.getIsWarn()) {
+//                sqlBuilder.append(" and record_data.is_warn  = 1 and record_data.is_audit = 0 ");
+//            } else {
+//                sqlBuilder.append(" and record_data.is_warn  = 0 ");
+//            }
+//        } else {
+//            sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 0))");
+//        }
+//        sqlBuilder.append(" and record_data.is_illegality = 0");
+//        //查询分页记录
+//        sqlBuilder.append(" group by record_data.id ");
+//
+//        sqlBuilder.append(" order by record_data.id desc");
+//        List<ExamRecordDataEntity> entities = jdbcTemplate.query(sqlBuilder.toString(), (rs, rowNum) -> getExamRecordDataEntityByResultSet(rs));
+//
+//        return examRecordEntityConvert.ofList(entities);
+//    }
 
     @Override
-    public Long getExamRecordWaitingAuditNextId(ExamRecordQuery query, Long examRecordDataId, String next) {
-        Check.isNull(query, "查询参数不能为空!");
+    public Long getExamRecordWaitingAuditNextId(UserDataRules uds,ExamRecordQuery query, Long examRecordDataId, String next) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return null;
+    	}
+    	Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
         //默认条件
         query.setIsWarn(true);
@@ -294,9 +299,8 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
         //查询条件
         StringBuilder sqlBuilder = new StringBuilder();
-        //待审核(按原先的sql,多了一个ip字段)
         sqlBuilder.append("select record_data.id from ec_oe_exam_record_data record_data where 1 = 1 ");
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(uds,query));
         if (query.getIsWarn() != null) {
             //只查有异常未审核
             if (query.getIsWarn()) {
@@ -341,31 +345,28 @@ public class ExamRecordServiceImpl implements ExamRecordService {
      * @param query
      * @return
      */
-    private Page<ExamRecordInfo> _getExamRecordDetailListForPage(ExamRecordQuery query) {
+    private Page<ExamRecordInfo> _getExamRecordDetailListForPage(UserDataRules uds,ExamRecordQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
         //查询条件
         StringBuilder sqlBuilder = new StringBuilder();
-        sqlBuilder.append(EXAM_RECORD_SQL);
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        sqlBuilder.append(EXAM_RECORD_FROM_SQL);
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(uds,query));
         sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
         sqlBuilder.append(" group by record_data.id ");
-        //根据ip查询
-        /*if (StringUtils.isNoneBlank(query.getIp())) {
-            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
-        }*/
-        sqlBuilder.append(" order by record_data.id desc");
         //分页条件
         int currentNum = (query.getPageNo() - 1) * query.getPageSize();
-        sqlBuilder.append(" limit " + currentNum + "," + query.getPageSize());
-        List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(sqlBuilder.toString(), new RowMapper<ExamRecordDataEntity>() {
+        List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(
+        		EXAM_RECORD_HEAD_SQL+sqlBuilder.toString()+" order by record_data.id desc limit " + currentNum + "," + query.getPageSize(), 
+        		new RowMapper<ExamRecordDataEntity>() {
             @Override
             public ExamRecordDataEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
                 return getExamRecordDataEntityByResultSet(rs);
             }
         });
         //查询总数
-        long total = countExamRecordDetailListForPage(query);
+//        long total = countExamRecordDetailListForPage(query);
+        long total=jdbcTemplate.queryForObject("SELECT count(1) FROM (SELECT record_data.id "+sqlBuilder.toString()+") temtb", Long.class);
         Pageable pageable = PageRequest.of(query.getPageNo() - 1, query.getPageSize());
         Page<ExamRecordDataEntity> page = new PageImpl<ExamRecordDataEntity>(examRecordDataList, pageable, total);
         Page<ExamRecordInfo> ret=examRecordEntityConvert.of(page);
@@ -411,7 +412,6 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         examRecordData.setFaceSuccessPercent(rs.getDouble("face_success_percent"));
         examRecordData.setFaceVerifyResult(IsSuccess.strToEnum(rs.getString("face_verify_result")));
         examRecordData.setBaiduFaceLivenessSuccessPercent(rs.getDouble("baidu_face_liveness_success_percent"));
-        examRecordData.setIp(rs.getString("ip"));
         examRecordData.setAuditUserName(rs.getString("audit_user_name"));
         examRecordData.setSwitchScreenCount(rs.getInt("switchScreenCount"));
         if(rs.getString("examStageId")!=null) {
@@ -421,14 +421,16 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         return examRecordData;
     }
 
-    private long countExamRecordDetailListForPage(ExamRecordQuery query) {
-        StringBuilder sqlBuilder = new StringBuilder();
-        sqlBuilder.append("select count(record_data.id) from ec_oe_exam_record_data record_data " +
-                "LEFT JOIN ec_oe_exam_audit AS audit ON record_data.id = audit.exam_record_data_id where 1=1");
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
-        sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
-        return jdbcTemplate.queryForObject(sqlBuilder.toString(), Long.class);
-    }
+//    private long countExamRecordDetailListForPage(ExamRecordQuery query) {
+//        StringBuilder sqlBuilder = new StringBuilder();
+//        sqlBuilder.append("select count(distinct record_data.id) from ec_oe_exam_record_data record_data " +
+//        		" LEFT JOIN ec_oe_exam_process_record AS process ON record_data.id = process.exam_record_data_id " +
+//                " LEFT JOIN ec_oe_exam_audit AS audit ON record_data.id = audit.exam_record_data_id where 1=1 ");
+//        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+//        sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
+//        sqlBuilder.append(" group by record_data.id ");
+//        return jdbcTemplate.queryForObject(sqlBuilder.toString(), Long.class);
+//    }
 
     /**
      * 构建查询 考试明细sql语句
@@ -436,7 +438,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
      * @param query
      * @return
      */
-    private StringBuilder buildExamRecordCommonSelectCondition(ExamRecordQuery query) {
+    private StringBuilder buildExamRecordCommonSelectCondition(UserDataRules uds,ExamRecordQuery query) {
         StringBuilder sql = new StringBuilder();
         if (query.getSwitchScreenCountStart() != null) {
         	sql.append(" and record_data.switch_screen_count >= " +query.getSwitchScreenCountStart());
@@ -529,13 +531,29 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         //是否有虚拟设备
         if (query.getHasVirtual() != null) {
             if (query.getHasVirtual()) {
-                sql.append(" and record_data.id in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId() + " and t2.has_virtual_camera=1 )");
+            	sql.append(" and camera.virtual_camera=1 ");
+//                sql.append(" and record_data.id in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId() + " and t2.has_virtual_camera=1 )");
             } else {
                 //原先写法会跟有虚拟设备的出现重复情况,用有虚拟设备的not in
                 //sql.append(" and record_data.id in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId()+" and (t2.has_virtual_camera=0 or t2.has_virtual_camera is null) )");
-                sql.append(" and record_data.id not in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId() + " and t2.has_virtual_camera=1 )");
+//                sql.append(" and record_data.id not in (select t1.id from ec_oe_exam_record_data t1 left join ec_oe_exam_capture t2 on t1.id=t2.exam_record_data_id where t1.exam_id =" + query.getExamId() + " and t2.has_virtual_camera=1 )");
+            	sql.append(" and (camera.virtual_camera=0 or camera.virtual_camera is null) ");
             }
         }
+        if (StringUtils.isNotBlank(query.getVirtualName())) {
+        	sql.append(" and camera.name='"+query.getVirtualName()+"' ");
+        }
+        
+        //ip
+        if (StringUtils.isNotBlank(query.getIp())) {
+        	sql.append(" and process.source_ip='"+query.getIp()+"' ");
+        }
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+            sql.append(" and record_data.org_id in (" + StringUtils.join(uds.getOrgRule().getRefIds(), ",")+") ");
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+            sql.append(" and record_data.course_id in (" + StringUtils.join(uds.getCourseRule().getRefIds(), ",")+") ");
+        }
         return sql;
     }
 
@@ -544,13 +562,16 @@ public class ExamRecordServiceImpl implements ExamRecordService {
      * 查询无异常的数据和有异常未审核的数据
      */
     @Override
-    public Page<ExamRecordInfo> getExamRecordList(ExamRecordQuery query) {
+    public Page<ExamRecordInfo> getExamRecordList(UserDataRules uds,ExamRecordQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return Page.empty();
+    	}
         Check.isNull(query, "查询参数不能为空!");
         //查询条件
         StringBuilder sqlBuilder = new StringBuilder();
         //待审核(按原先的sql,多了一个ip字段)
-        sqlBuilder.append(EXAM_RECORD_SQL);
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        sqlBuilder.append(EXAM_RECORD_FROM_SQL);
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(uds,query));
         if (query.getIsWarn() != null) {
             //只查有异常未审核
             if (query.getIsWarn()) {
@@ -564,22 +585,20 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         sqlBuilder.append(" and record_data.is_illegality = 0");
         //查询分页记录
         sqlBuilder.append(" group by record_data.id ");
-        //根据ip查询
-        /*if (StringUtils.isNoneBlank(query.getIp())) {
-            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
-        }*/
-        sqlBuilder.append(" order by record_data.id desc");
+//        sqlBuilder.append(" order by record_data.id desc");
         //分页条件
         int currentNum = (query.getPageNo() - 1) * query.getPageSize();
-        sqlBuilder.append(" limit " + currentNum + "," + query.getPageSize());
-        List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(sqlBuilder.toString(), new RowMapper<ExamRecordDataEntity>() {
+//        sqlBuilder.append(" limit " + currentNum + "," + query.getPageSize());
+        List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(
+        		EXAM_RECORD_HEAD_SQL+sqlBuilder.toString()+" order by record_data.id desc limit " + currentNum + "," + query.getPageSize(), new RowMapper<ExamRecordDataEntity>() {
             @Override
             public ExamRecordDataEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
                 return getExamRecordDataEntityByResultSet(rs);
             }
         });
         //查询总数
-        long total = countExamRecordListForPage(query);
+//        long total = countExamRecordListForPage(query);
+        long total=jdbcTemplate.queryForObject("SELECT count(1) FROM (SELECT record_data.id "+sqlBuilder.toString()+") temtb", Long.class);
         Pageable pageable = PageRequest.of(query.getPageNo() - 1, query.getPageSize());
 
         Page<ExamRecordDataEntity> page = new PageImpl<ExamRecordDataEntity>(examRecordDataList, pageable, total);
@@ -590,24 +609,25 @@ public class ExamRecordServiceImpl implements ExamRecordService {
      * 监考待审:数量查询
      * 查询无异常的数据和有异常未审核的数据
      */
-    private long countExamRecordListForPage(ExamRecordQuery query) {
-        StringBuilder sqlBuilder = new StringBuilder();
-        sqlBuilder.append("select count(record_data.id) from ec_oe_exam_record_data record_data where 1=1");
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
-        if (query.getIsWarn() != null) {
-            if (query.getIsWarn()) {
-                sqlBuilder.append(" and record_data.is_warn  = 1 and record_data.is_audit = 0 ");
-            } else {
-                sqlBuilder.append(" and record_data.is_warn  = 0 ");
-            }
-        } else {
-            sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 0))");
-        }
-        sqlBuilder.append(" and record_data.is_illegality = 0");
-        return jdbcTemplate.queryForObject(sqlBuilder.toString(), Long.class);
-    }
-
-    @Override
+//    private long countExamRecordListForPage(ExamRecordQuery query) {
+//        StringBuilder sqlBuilder = new StringBuilder();
+//        sqlBuilder.append("select count(record_data.id) from ec_oe_exam_record_data record_data where 1=1");
+//        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+//        if (query.getIsWarn() != null) {
+//            if (query.getIsWarn()) {
+//                sqlBuilder.append(" and record_data.is_warn  = 1 and record_data.is_audit = 0 ");
+//            } else {
+//                sqlBuilder.append(" and record_data.is_warn  = 0 ");
+//            }
+//        } else {
+//            sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 0))");
+//        }
+//        sqlBuilder.append(" and record_data.is_illegality = 0");
+//        return jdbcTemplate.queryForObject(sqlBuilder.toString(), Long.class);
+//    }
+
+    @SuppressWarnings("unchecked")
+	@Override
     public List<Long> getExamRecordingStudentIds(Long examId) {
         Check.isNull(examId, "考试ID不能为空!");
 
@@ -701,17 +721,20 @@ public class ExamRecordServiceImpl implements ExamRecordService {
     }
 
     @Override
-    public Long existsWarnExamRecordDetail(ExamRecordQuery query) {
+    public Long existsWarnExamRecordDetail(UserDataRules uds,ExamRecordQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return 0L;
+    	}
         StringBuilder sqlBuilder = new StringBuilder();
         sqlBuilder.append("select count(record_data.id) from ec_oe_exam_record_data record_data "
                 + " where 1=1 ");
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(uds,query));
         sqlBuilder.append(" and record_data.is_warn = 1 and record_data.is_audit = 0");
         return jdbcTemplate.queryForObject(sqlBuilder.toString(), Long.class);
     }
 
     @Override
-    public List<ExamRecordInfo> getExamRecordDetailListForAsync(ExamRecordQuery query) {
+    public List<ExamRecordInfo> getExamRecordDetailListForAsync(UserDataRules uds,ExamRecordQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
         query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
@@ -719,7 +742,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         List<ExamRecordInfo> examRecordDataList = new ArrayList<ExamRecordInfo>();
         Long startId = 0L;
         for (; ; ) {
-            List<ExamRecordInfo> tem = getExamRecordDetailPageForExport(query, startId);
+            List<ExamRecordInfo> tem = getExamRecordDetailPageForExport(uds,query, startId);
             if (tem == null || tem.size() == 0) {
                 break;
             } else {
@@ -737,6 +760,7 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         String examType = examRecordDataList.get(0).getExamType();
         if (ExamType.ONLINE.name().equals(examType) || ExamType.ONLINE_HOMEWORK.name().equals(examType)) {
             setVirtualCameraNames(examRecordDataList);
+            setIps(examRecordDataList);
         }
         fillStage(examRecordDataList);
         return examRecordDataList;
@@ -813,36 +837,17 @@ public class ExamRecordServiceImpl implements ExamRecordService {
             public void setData(List<ExamRecordInfo> dataList) {
                 List<Long> ids = dataList.stream().map(dto -> dto.getId()).distinct().collect(Collectors.toList());
                 Map<Long, Set<String>> map = new HashMap<>();
-                List<ExamCaptureEntity> examCaptureList = examCaptureRepo.findByExamRecordDataIdIn(ids);
+                List<ExamCaptureCameraInfoEntity> examCaptureList = examCaptureCameraInfoRepo.findByExamRecordDataIdInAndVirtualCamera(ids,true);
                 if (examCaptureList != null && examCaptureList.size() > 0) {
-                    for (ExamCaptureEntity examCapture : examCaptureList) {
+                    for (ExamCaptureCameraInfoEntity examCapture : examCaptureList) {
                         Set<String> virtualCameraNames = map.get(examCapture.getExamRecordDataId());
                         if (virtualCameraNames == null) {
                             virtualCameraNames = new HashSet<String>();
                             map.put(examCapture.getExamRecordDataId(), virtualCameraNames);
                         }
-                        String cameraInfos = examCapture.getCameraInfos();
+                        String cameraInfos = examCapture.getName();
                         if (StringUtils.isNotBlank(cameraInfos)) {
-                            JSONArray jsonArray;
-                            try {
-                                jsonArray = new JSONArray(cameraInfos);
-                                for (int i = 0; i < jsonArray.length(); i++) {
-                                    try {
-                                        JSONObject jsonObject = (JSONObject) jsonArray.get(i);
-                                        if (StringUtils.isBlank(jsonObject.getString("pid"))) {
-                                            virtualCameraNames.add(jsonObject.getString("name"));
-                                        }
-                                    } catch (JSONException e) {
-                                        //主要针对json数组最后的空对象处理,不影响业务
-                                        LOG.error("抓拍照片格式不正确", e);
-                                        continue;
-                                    }
-                                }
-                            } catch (JSONException e) {
-                                LOG.error("获取虚拟摄像头名称失败", e);
-                                throw new StatusException("ExamCaptureService-001", "获取虚拟摄像头名称失败");
-                            }
-
+                            virtualCameraNames.add(cameraInfos);
                         }
                     }
                 }
@@ -854,6 +859,35 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         };
         tool.setDataForBatch(examRecordDataList, 100);
     }
+    
+    private void setIps(List<ExamRecordInfo> examRecordDataList) {
+        BatchSetDataUtil<ExamRecordInfo> tool = new BatchSetDataUtil<ExamRecordInfo>() {
+            @Override
+            public void setData(List<ExamRecordInfo> dataList) {
+                List<Long> ids = dataList.stream().map(dto -> dto.getId()).distinct().collect(Collectors.toList());
+                Map<Long, Set<String>> map = new HashMap<>();
+                List<ExamProcessRecordEntity> list = examProcessRecordRepo.findByExamRecordDataIdIn(ids);
+                if (list != null && list.size() > 0) {
+                    for (ExamProcessRecordEntity ep : list) {
+                        Set<String> ips = map.get(ep.getExamRecordDataId());
+                        if (ips == null) {
+                        	ips = new HashSet<String>();
+                            map.put(ep.getExamRecordDataId(), ips);
+                        }
+                        String ip = ep.getSourceIp();
+                        if (StringUtils.isNotBlank(ip)) {
+                            ips.add(ip);
+                        }
+                    }
+                }
+                for (ExamRecordInfo erInfo : dataList) {
+                    erInfo.setIp(getStrFromSet(map.get(erInfo.getId())));
+                }
+            }
+
+        };
+        tool.setDataForBatch(examRecordDataList, 100);
+    }
 
     private String getStrFromSet(Set<String> set) {
         if (set == null || set.size() == 0) {
@@ -921,10 +955,10 @@ public class ExamRecordServiceImpl implements ExamRecordService {
 
     }
 
-    private List<ExamRecordInfo> getExamRecordDetailPageForExport(ExamRecordQuery query, Long startId) {
+    private List<ExamRecordInfo> getExamRecordDetailPageForExport(UserDataRules uds,ExamRecordQuery query, Long startId) {
         StringBuilder sqlBuilder = new StringBuilder();
         sqlBuilder.append(EXAM_RECORD_EXPORT_SQL);
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+        sqlBuilder.append(buildExamRecordCommonSelectCondition(uds,query));
         sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
         sqlBuilder.append(" and record_data.id >" + startId);
         sqlBuilder.append(" group by record_data.id ");
@@ -939,68 +973,68 @@ public class ExamRecordServiceImpl implements ExamRecordService {
         return ret;
     }
 
-    @Override
-    public List<ExamRecordInfo> getExamRecordDetailList(ExamRecordQuery query) {
-        Check.isNull(query, "查询参数不能为空!");
-        query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
-        query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
-
-        StringBuilder sqlBuilder = new StringBuilder();
-        sqlBuilder.append(EXAM_RECORD_SQL);
-        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
-        sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
-        sqlBuilder.append(" group by record_data.id ");
-        //根据ip查询
-        /*if (StringUtils.isNoneBlank(query.getIp())) {
-            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
-        }*/
-        sqlBuilder.append(" order by record_data.id desc");
-
-        List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(sqlBuilder.toString(), new RowMapper<ExamRecordDataEntity>() {
-            @Override
-            public ExamRecordDataEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
-                return getExamRecordDataEntityByResultSet(rs);
-            }
-        });
-
-        List<ExamRecordInfo> examRecordInfoList = examRecordEntityConvert.of(examRecordDataList);
-        return this.loadExamRecordDetailData(examRecordInfoList, query.getExamId());
-    }
-
-    private List<ExamRecordInfo> loadExamRecordDetailData(List<ExamRecordInfo> list, Long examId) {
-        if (list == null || list.size() == 0) {
-            return new ArrayList<ExamRecordInfo>();
-        }
-        //缓存
-        Map<String, Object> cahcheMap = new HashMap<String, Object>();
-
-        list.forEach(examRecordInfo -> {
-            //获取考试名称
-            ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
-            examRecordInfo.setExamName(examBean.getName());
-
-            ExamStudentInfo examStudent = (ExamStudentInfo) cahcheMap.get("examStudentinfo_" + examRecordInfo.getExamStudentId());
-            if (examStudent == null) {
-                examStudent = examStudentService.getExamStudentInfo(examRecordInfo.getExamStudentId());
-                cahcheMap.put("examStudentinfo_" + examRecordInfo.getExamStudentId(), examStudent);
-            }
-
-            String photoNumber = localCacheService.getStudentPhotoNumber(cahcheMap, examRecordInfo.getStudentId());
-            examRecordInfo.setPhone(photoNumber);//电话号码
-            if (examStudent != null) {
-                examRecordInfo.setSpecialtyName(examStudent.getSpecialtyName());
-                examRecordInfo.setGrade(examStudent.getGrade());
-            }
-            Map<String, String> data = this.getPaperScore(examRecordInfo.getDataId());
-            //试卷总分
-            examRecordInfo.setPaperTotalScore(data.get("paperTotalScore"));
-            //客观题总分
-            examRecordInfo.setObjectiveTotalScore(data.get("objectiveTotalScore"));
-            //主观题总分
-            examRecordInfo.setSubjectiveTotalScore(data.get("subjectiveTotalScore"));
-        });
-        return list;
-    }
+//    @Override
+//    public List<ExamRecordInfo> getExamRecordDetailList(ExamRecordQuery query) {
+//        Check.isNull(query, "查询参数不能为空!");
+//        query.addRecordStatus(ExamRecordStatus.EXAM_END.name());
+//        query.addRecordStatus(ExamRecordStatus.EXAM_OVERDUE.name());
+//
+//        StringBuilder sqlBuilder = new StringBuilder();
+//        sqlBuilder.append(EXAM_RECORD_SQL);
+//        sqlBuilder.append(buildExamRecordCommonSelectCondition(query));
+//        sqlBuilder.append(" and ((record_data.is_warn = 0) OR (record_data.is_warn  = 1 and record_data.is_audit = 1))");
+//        sqlBuilder.append(" group by record_data.id ");
+//        //根据ip查询
+//        /*if (StringUtils.isNoneBlank(query.getIp())) {
+//            sqlBuilder.append(" having ip like '%"+query.getIp()+"%' ");
+//        }*/
+//        sqlBuilder.append(" order by record_data.id desc");
+//
+//        List<ExamRecordDataEntity> examRecordDataList = jdbcTemplate.query(sqlBuilder.toString(), new RowMapper<ExamRecordDataEntity>() {
+//            @Override
+//            public ExamRecordDataEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
+//                return getExamRecordDataEntityByResultSet(rs);
+//            }
+//        });
+//
+//        List<ExamRecordInfo> examRecordInfoList = examRecordEntityConvert.of(examRecordDataList);
+//        return this.loadExamRecordDetailData(examRecordInfoList, query.getExamId());
+//    }
+
+//    private List<ExamRecordInfo> loadExamRecordDetailData(List<ExamRecordInfo> list, Long examId) {
+//        if (list == null || list.size() == 0) {
+//            return new ArrayList<ExamRecordInfo>();
+//        }
+//        //缓存
+//        Map<String, Object> cahcheMap = new HashMap<String, Object>();
+//
+//        list.forEach(examRecordInfo -> {
+//            //获取考试名称
+//            ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+//            examRecordInfo.setExamName(examBean.getName());
+//
+//            ExamStudentInfo examStudent = (ExamStudentInfo) cahcheMap.get("examStudentinfo_" + examRecordInfo.getExamStudentId());
+//            if (examStudent == null) {
+//                examStudent = examStudentService.getExamStudentInfo(examRecordInfo.getExamStudentId());
+//                cahcheMap.put("examStudentinfo_" + examRecordInfo.getExamStudentId(), examStudent);
+//            }
+//
+//            String photoNumber = localCacheService.getStudentPhotoNumber(cahcheMap, examRecordInfo.getStudentId());
+//            examRecordInfo.setPhone(photoNumber);//电话号码
+//            if (examStudent != null) {
+//                examRecordInfo.setSpecialtyName(examStudent.getSpecialtyName());
+//                examRecordInfo.setGrade(examStudent.getGrade());
+//            }
+//            Map<String, String> data = this.getPaperScore(examRecordInfo.getDataId());
+//            //试卷总分
+//            examRecordInfo.setPaperTotalScore(data.get("paperTotalScore"));
+//            //客观题总分
+//            examRecordInfo.setObjectiveTotalScore(data.get("objectiveTotalScore"));
+//            //主观题总分
+//            examRecordInfo.setSubjectiveTotalScore(data.get("subjectiveTotalScore"));
+//        });
+//        return list;
+//    }
 
     @Override
     public List<ExamStudentQuestionScoreInfo> getExamStudentQuestionScoreList(Long examId, String courseCode) {

+ 0 - 4
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreQueueServiceImpl.java

@@ -5,7 +5,6 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamScorePushQueue;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamScoreQueueStatus;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScorePushService;
 import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreQueueService;
-import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
 import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
@@ -26,9 +25,6 @@ public class ExamScoreQueueServiceImpl implements ExamScoreQueueService {
     @Autowired
     private ExamScorePushQueueRepo examScorePushQueueRepo;
 
-    @Autowired
-    private GainBaseDataService gainBaseDataService;
-
     @Override
     public void disposeScoreQueue() {
         List<ExamScorePushQueue> examScorePushQueues = examScorePushQueueRepo.findExamScorePushQueue();

+ 176 - 37
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamScoreServiceImpl.java

@@ -7,6 +7,26 @@
 
 package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.Pageable;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import com.google.common.collect.Lists;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.DateUtil;
 import cn.com.qmth.examcloud.core.oe.admin.base.jpa.SpecUtils;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
@@ -19,7 +39,12 @@ import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.entity.ExamStudentFinalScoreEntity;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.CourseLevel;
 import cn.com.qmth.examcloud.core.oe.admin.dao.enums.ExamRecordStatus;
-import cn.com.qmth.examcloud.core.oe.admin.service.*;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamRecordService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentFinalScoreService;
+import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
+import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreEntityConvert;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examscore.ExamScoreQuery;
@@ -31,25 +56,11 @@ import cn.com.qmth.examcloud.support.cache.bean.ExamSettingsCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.ExamStageCacheBean;
 import cn.com.qmth.examcloud.support.cache.bean.OrgCacheBean;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.examing.ExamBoss;
+import cn.com.qmth.examcloud.support.examing.ExamRecordData;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
-import com.google.common.collect.Lists;
-
-import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.domain.Page;
-import org.springframework.data.domain.PageImpl;
-import org.springframework.data.domain.Pageable;
-import org.springframework.jdbc.core.JdbcTemplate;
-import org.springframework.jdbc.core.RowMapper;
-import org.springframework.stereotype.Service;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
+import cn.com.qmth.examcloud.support.redis.RedisKeyHelper;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
 
 /**
  * 考试分数相关接口
@@ -78,11 +89,14 @@ public class ExamScoreServiceImpl implements ExamScoreService {
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
+    @Autowired
+    private RedisClient redisClient;
+
     @Autowired
     private ExamStudentFinalScoreService examStudentFinalScoreService;
 
     @Override
-    public Page<ExamScoreInfo> getExamScoreList(ExamScoreQuery query) {
+    public Page<ExamScoreInfo> getExamScoreList(UserDataRules uds,ExamScoreQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
         Pageable pageable = SpecUtils.buildPageable(query.getPageNo(), query.getPageSize());
@@ -93,7 +107,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
                 getDefaultCachedExamProperty(query.getExamId(), ExamProperties.MARKING_TYPE.name()).getValue();
 
         //获取考生列表
-        Page<ExamStudentInfo> page = examStudentService.getExamStudentListPage(ExamScoreEntityConvert.of(query));
+        Page<ExamStudentInfo> page = examStudentService.getExamStudentListPage(uds,ExamScoreEntityConvert.of(query));
         List<ExamStudentInfo> examStudentList = page.getContent();
         if (examStudentList == null || examStudentList.size() == 0) {
             return new PageImpl<ExamScoreInfo>(Lists.newArrayList(), pageable, page.getTotalElements());
@@ -163,7 +177,10 @@ public class ExamScoreServiceImpl implements ExamScoreService {
     }
 
     @Override
-    public List<ExamScoreInfo> exportExamScoreListForAsync(ExamScoreQuery query) {
+    public List<ExamScoreInfo> exportExamScoreListForAsync(UserDataRules uds,ExamScoreQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return Lists.newArrayList();
+    	}
         Check.isNull(query, "查询参数不能为空!");
         Check.isNull(query.getExamId(), "请先选择考试批次!");
 
@@ -171,12 +188,12 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         String markingType = ExamCacheTransferHelper.
                 getDefaultCachedExamProperty(query.getExamId(), ExamProperties.MARKING_TYPE.name()).getValue();
         if (query.getStartLimit() != null && query.getEndLimit() != null) {
-            return getExamStudentInfoListOfScoreExport(query, markingType);
+            return getExamStudentInfoListOfScoreExport(uds,query, markingType);
         } else {
             List<ExamStudentEntity> examStudentList = new ArrayList<ExamStudentEntity>();
             Long startId = 0L;
             for (; ; ) {
-                List<ExamStudentEntity> tem = getExamStudentInfoListOfScoreExportByPage(query, markingType, startId);
+                List<ExamStudentEntity> tem = getExamStudentInfoListOfScoreExportByPage(uds,query, markingType, startId);
                 if (tem == null || tem.size() == 0) {
                     break;
                 } else {
@@ -201,7 +218,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         }
     }
 
-    private List<ExamStudentEntity> getExamStudentInfoListOfScoreExportByPage(ExamScoreQuery query, String markingType, Long startId) {
+    private List<ExamStudentEntity> getExamStudentInfoListOfScoreExportByPage(UserDataRules uds,ExamScoreQuery query, String markingType, Long startId) {
         //查询条件
         StringBuffer sql = new StringBuffer();
         sql.append("select id,exam_student_id,exam_id,course_id,course_code,course_level,finished,student_id,student_code,student_name,identity_number"
@@ -234,6 +251,12 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         if (query.getFinished() != null) {
             sql.append(" and finished= " + query.getFinished());
         }
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+        	sql.append(" and org_id in (" + StringUtils.join(uds.getOrgRule().getRefIds(), ",")+") ");
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+        	sql.append(" and course_id in (" + StringUtils.join(uds.getCourseRule().getRefIds(), ",")+") ");
+        }
         sql.append(" and id >" + startId);
         sql.append(" order by id limit 500 ");
 
@@ -247,16 +270,16 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         return examStudentList;
     }
 
-    @Override
-    public List<ExamScoreInfo> exportExamScoreList(ExamScoreQuery query) {
-        Check.isNull(query, "查询参数不能为空!");
-        Check.isNull(query.getExamId(), "请先选择考试批次!");
-
-        //阅卷方式
-        String markingType = ExamCacheTransferHelper.
-                getDefaultCachedExamProperty(query.getExamId(), ExamProperties.MARKING_TYPE.name()).getValue();
-        return getExamStudentInfoListOfScoreExport(query, markingType);
-    }
+//    @Override
+//    public List<ExamScoreInfo> exportExamScoreList(ExamScoreQuery query) {
+//        Check.isNull(query, "查询参数不能为空!");
+//        Check.isNull(query.getExamId(), "请先选择考试批次!");
+//
+//        //阅卷方式
+//        String markingType = ExamCacheTransferHelper.
+//                getDefaultCachedExamProperty(query.getExamId(), ExamProperties.MARKING_TYPE.name()).getValue();
+//        return getExamStudentInfoListOfScoreExport(query, markingType);
+//    }
 
     @Override
     public List<ExamScoreEntity> getAllExamScoreList(Long examId, String identityNumber, Long courseId) {
@@ -270,7 +293,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         return examScoreRepo.findByExamRecordDataIdIn(examRecordDataIds);
     }
 
-    private List<ExamScoreInfo> getExamStudentInfoListOfScoreExport(ExamScoreQuery query, String markingType) {
+    private List<ExamScoreInfo> getExamStudentInfoListOfScoreExport(UserDataRules uds,ExamScoreQuery query, String markingType) {
         Check.isNull(query, "查询参数不能为空!");
         //查询条件
         StringBuffer sql = new StringBuffer();
@@ -304,6 +327,12 @@ public class ExamScoreServiceImpl implements ExamScoreService {
         if (query.getFinished() != null) {
             sql.append(" and finished= " + query.getFinished());
         }
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+        	sql.append(" and org_id in (" + StringUtils.join(uds.getOrgRule().getRefIds(), ",")+") ");
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+        	sql.append(" and course_id in (" + StringUtils.join(uds.getCourseRule().getRefIds(), ",")+") ");
+        }
         sql.append(" order by update_time desc");
 
         if (query.getStartLimit() != null && query.getEndLimit() != null) {
@@ -516,6 +545,8 @@ public class ExamScoreServiceImpl implements ExamScoreService {
 
     @Override
     public List<ObjectiveScoreInfo> queryObjectiveScoreList(Long examStudentId) {
+        Check.isNull(examStudentId, "examStudentId 不能为空");
+
         List<ExamRecordDataEntity> examRecordDataList = examRecordDataRepo.findByExamStudentId(examStudentId);
         //过滤已完成的考试记录(包括违纪的)
         examRecordDataList = examRecordDataList.stream().filter((o -> {
@@ -525,7 +556,7 @@ public class ExamScoreServiceImpl implements ExamScoreService {
                     o.getExamRecordStatus() == ExamRecordStatus.EXAM_AUTO_HAND_IN;
         })).collect(Collectors.toList());
 
-        List<ObjectiveScoreInfo> objectiveScoreInfoList = new ArrayList<ObjectiveScoreInfo>();
+        List<ObjectiveScoreInfo> objectiveScoreInfoList = new ArrayList<>();
         for (ExamRecordDataEntity examRecordDataEntity : examRecordDataList) {
             ObjectiveScoreInfo objectiveScoreInfo = new ObjectiveScoreInfo();
             objectiveScoreInfo.setExamRecordDataId(examRecordDataEntity.getId());
@@ -556,9 +587,117 @@ public class ExamScoreServiceImpl implements ExamScoreService {
             }
             objectiveScoreInfoList.add(objectiveScoreInfo);
         }
+
+        processObjectiveScoreList(examStudentId, objectiveScoreInfoList);
+
         return objectiveScoreInfoList;
     }
 
+    private void processObjectiveScoreList(Long examStudentId, List<ObjectiveScoreInfo> resultList) {
+        //如果有未处理完成的考试记录,需要将未处理的考试记录数据添加到列表中
+        String examBossKey = RedisKeyHelper.getBuilder().examBossKey(examStudentId);
+        ExamBoss examBoss = redisClient.get(examBossKey, ExamBoss.class);
+        if (null != examBoss) {
+            //未完全同步的考试记录id
+            List<Long> unSyncedExamRecordDataIds = examBoss.getExamRecordDataIds();
+            if (null != unSyncedExamRecordDataIds && !unSyncedExamRecordDataIds.isEmpty()) {
+                //正序排列
+                unSyncedExamRecordDataIds.sort(Long::compareTo);
+
+                //已考次数
+                Integer examUsedNum = 0;
+
+                for (Long examRecordDataId : unSyncedExamRecordDataIds) {
+                    ExamRecordData examRecordData =
+                            redisClient.get(RedisKeyHelper.getBuilder().examRecordDataKey(examRecordDataId),
+                                    ExamRecordData.class);
+                    if (null == examRecordData) {
+                        throw new StatusException("100001", "考试记录的缓存数据有误");
+                    }
+
+                    //考试中的数据不展示
+                    if (cn.com.qmth.examcloud.support.enums.ExamRecordStatus.EXAM_ING == examRecordData.getExamRecordStatus()) {
+                        continue;
+                    }
+
+                    if (!resultList.isEmpty()) {
+                        examUsedNum = resultList.get(resultList.size() - 1).getExamOrder();
+                    }
+
+                    ObjectiveScoreInfo cachedObjectiveScoreInfo = getCachedObjectiveScoreInfo(examRecordData);
+                    cachedObjectiveScoreInfo.setExamOrder(
+                            getExamOrder(examRecordData.getExamId(), examRecordData.getStudentId(), examUsedNum));
+
+                    resultList.add(cachedObjectiveScoreInfo);
+                }
+            }
+        }
+    }
+
+    private ObjectiveScoreInfo getCachedObjectiveScoreInfo(final ExamRecordData examRecordData) {
+        ObjectiveScoreInfo objectiveScoreInfo = new ObjectiveScoreInfo();
+        objectiveScoreInfo.setExamRecordDataId(examRecordData.getId());
+        objectiveScoreInfo.setStartTime(examRecordData.getStartTime());
+        objectiveScoreInfo.setEndTime(examRecordData.getEndTime());
+
+        //如果考试没有结束,则只能返回部分数据
+        if (!isExamRecordEnded(examRecordData)) {
+            objectiveScoreInfo.setIsExamEnded(false);
+            return objectiveScoreInfo;
+        } else {
+            objectiveScoreInfo.setIsExamEnded(true);
+        }
+
+        if (examRecordData.getIsIllegality() == null || !examRecordData.getIsIllegality()) {
+            if ((null != examRecordData.getIsWarn() && !examRecordData.getIsWarn())
+                    || (null != examRecordData.getIsWarn() && examRecordData.getIsWarn()
+                    && null != examRecordData.getIsAudit() && examRecordData.getIsAudit())) {
+                objectiveScoreInfo.setIsAuditing(true);
+
+                //缓存中的分数是存储在临时考试记录表中的,所以需要从考试记录缓存中取
+                objectiveScoreInfo.setObjectiveScore(examRecordData.getObjectiveScore());
+            } else {
+                objectiveScoreInfo.setIsAuditing(false);
+            }
+
+            objectiveScoreInfo.setIsIllegality(false);
+        } else {
+            objectiveScoreInfo.setIsIllegality(true);
+        }
+
+        return objectiveScoreInfo;
+    }
+
+    /**
+     * 计算考试次数
+     *
+     * @param examId      考试id
+     * @param studentId   学生id
+     * @param usedExamNum 已考次数
+     * @return
+     */
+    private Integer getExamOrder(Long examId, Long studentId, Integer usedExamNum) {
+        ExamSettingsCacheBean cachedExam = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+        Integer canExamTimes = cachedExam.getExamTimes() == null ? 0 : cachedExam.getExamTimes().intValue();//可考次数
+
+        //超过可考次数,始终为可考次数+1
+        if (usedExamNum > canExamTimes) {
+            return canExamTimes;
+        }
+
+        return usedExamNum;
+    }
+
+    private boolean isExamRecordEnded(ExamRecordData examRecordData) {
+        //如果考试记录状态为已处理,则直接返回true.
+        if (examRecordData.getExamRecordStatus() == cn.com.qmth.examcloud.support.enums.ExamRecordStatus.EXAM_END ||
+                examRecordData.getExamRecordStatus() == cn.com.qmth.examcloud.support.enums.ExamRecordStatus.EXAM_OVERDUE ||
+                examRecordData.getExamRecordStatus() == cn.com.qmth.examcloud.support.enums.ExamRecordStatus.EXAM_INVALID) {
+            return true;
+        }
+        return false;
+    }
+
     private boolean isExamRecordEnded(ExamRecordDataEntity examRecordData) {
         //如果考试记录状态为已处理,则直接返回true.
         if (examRecordData.getExamRecordStatus() == ExamRecordStatus.EXAM_END ||

+ 90 - 33
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExamStudentServiceImpl.java

@@ -47,6 +47,7 @@ import com.google.common.collect.Lists;
 
 import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.StringUtil;
 import cn.com.qmth.examcloud.core.basic.api.StudentCloudService;
@@ -74,6 +75,7 @@ import cn.com.qmth.examcloud.core.oe.admin.service.ExamStudentService;
 import cn.com.qmth.examcloud.core.oe.admin.service.GainBaseDataService;
 import cn.com.qmth.examcloud.core.oe.admin.service.LocalCacheService;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.OnHandExamInfo;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.UserDataRules;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.CourseProgressInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentEntityConvert;
@@ -86,10 +88,7 @@ import cn.com.qmth.examcloud.core.oe.admin.service.cache.ExamStudentCache;
 import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
 import cn.com.qmth.examcloud.examwork.api.ExamStudentCloudService;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamSpecialSettingsBean;
-import cn.com.qmth.examcloud.examwork.api.bean.ExamStudentBean;
-import cn.com.qmth.examcloud.examwork.api.request.GetExamStudentReq;
 import cn.com.qmth.examcloud.examwork.api.request.GetOngoingExamListReq;
-import cn.com.qmth.examcloud.examwork.api.response.GetExamStudentResp;
 import cn.com.qmth.examcloud.examwork.api.response.GetOngoingExamListResp;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
@@ -255,7 +254,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public Page<ExamStudentInfo> getExamStudentListPage(ExamStudentQuery query) {
+    public Page<ExamStudentInfo> getExamStudentListPage(UserDataRules uds,ExamStudentQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return Page.empty();
+    	}
         Check.isNull(query, "查询参数不能为空!");
 
         ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
@@ -272,7 +274,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         sql.append(",student_id,student_code,student_name,identity_number"
                 + ",info_collector,root_org_id,org_id,paper_type,used_num,extra_num"
                 + ",specialty_code,specialty_name,grade,t1.exam_stage_id from ec_oe_exam_student t1 where 1=1 ");
-        sql.append(selectExamStudentConfitionSql(query, examBean.getExamType()));
+        sql.append(selectExamStudentConfitionSql(uds,query, examBean.getExamType()));
         sql.append(" order by id desc");
         int currentNum = (query.getPageNo() - 1) * query.getPageSize();
         sql.append(" limit " + currentNum + "," + query.getPageSize());
@@ -305,7 +307,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         }
         cahcheMap.clear();
         Pageable pageable = SpecUtils.buildPageable(query.getPageNo(), query.getPageSize());
-        long totalSize = countExamStudent(query, examBean.getExamType());
+        long totalSize = countExamStudent(uds,query, examBean.getExamType());
         fillStage(examStudentInfoList);
         return new PageImpl<>(examStudentInfoList, pageable, totalSize);
     }
@@ -324,16 +326,16 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     	}
     }
 
-    private long countExamStudent(ExamStudentQuery query, String examType) {
+    private long countExamStudent(UserDataRules uds,ExamStudentQuery query, String examType) {
         //查询条件
         StringBuffer sql = new StringBuffer();
         sql.append("select count(t1.id) from ec_oe_exam_student t1 where 1=1 ");
-        sql.append(selectExamStudentConfitionSql(query, examType));
+        sql.append(selectExamStudentConfitionSql(uds,query, examType));
         return jdbcTemplate.queryForObject(sql.toString(), Long.class);
     }
 
     @Override
-    public List<ExamStudentInfo> getExamStudentInfoListForAsync(ExamStudentQuery query) {
+    public List<ExamStudentInfo> getExamStudentInfoListForAsync(UserDataRules uds,ExamStudentQuery query) {
         Check.isNull(query, "查询参数不能为空!");
         ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
 
@@ -341,7 +343,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         int pageNo = 1;
         int pageSize = 500;
         for (; ; ) {
-            List<ExamStudentEntity> tem = getExamStudentInfoListByPage(query, examBean, pageNo, pageSize);
+            List<ExamStudentEntity> tem = getExamStudentInfoListByPage(uds,query, examBean, pageNo, pageSize);
             if (tem == null || tem.size() == 0) {
                 break;
             } else {
@@ -426,7 +428,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         tool.setDataForBatch(dataList, 100);
     }
 
-    private List<ExamStudentEntity> getExamStudentInfoListByPage(ExamStudentQuery query, ExamSettingsCacheBean examBean, int pageNo, int pageSize) {
+    private List<ExamStudentEntity> getExamStudentInfoListByPage(UserDataRules uds,ExamStudentQuery query, ExamSettingsCacheBean examBean, int pageNo, int pageSize) {
         //查询条件
         StringBuffer sql = new StringBuffer();
         sql.append("select id,exam_student_id,exam_id,course_id,course_code,course_level");
@@ -439,7 +441,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         sql.append(",student_id,student_code,student_name,identity_number"
                 + ",info_collector,root_org_id,org_id,paper_type,used_num,extra_num"
                 + ",specialty_code,specialty_name,grade,exam_stage_id from ec_oe_exam_student t1 where 1=1 ");
-        sql.append(selectExamStudentConfitionSql(query, examBean.getExamType()));
+        sql.append(selectExamStudentConfitionSql(uds,query, examBean.getExamType()));
         int currentNum = (pageNo - 1) * pageSize;
         sql.append(" order by id limit " + currentNum + "," + pageSize);
 
@@ -454,7 +456,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public List<ExamStudentInfo> getExamStudentInfoList(ExamStudentQuery query) {
+    public List<ExamStudentInfo> getExamStudentInfoList(UserDataRules uds,ExamStudentQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return Lists.newArrayList();
+    	}
         Check.isNull(query, "查询参数不能为空!");
         ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
 
@@ -470,7 +475,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         sql.append(",student_id,student_code,student_name,identity_number"
                 + ",info_collector,root_org_id,org_id,paper_type,used_num,extra_num"
                 + ",specialty_code,specialty_name,grade,t1.exam_stage_id from ec_oe_exam_student t1 where 1=1 ");
-        sql.append(selectExamStudentConfitionSql(query, examBean.getExamType()));
+        sql.append(selectExamStudentConfitionSql(uds,query, examBean.getExamType()));
         sql.append(" order by id desc");
 
         List<ExamStudentEntity> examStudentList = jdbcTemplate.query(sql.toString(), new RowMapper<ExamStudentEntity>() {
@@ -504,7 +509,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         return examStudentInfoList;
     }
 
-    private StringBuffer selectExamStudentConfitionSql(ExamStudentQuery query, String examType) {
+    private StringBuffer selectExamStudentConfitionSql(UserDataRules uds,ExamStudentQuery query, String examType) {
         StringBuffer sql = new StringBuffer();
         if (query.getOrgId() != null) {
             sql.append(" and org_id=" + query.getOrgId());
@@ -558,6 +563,12 @@ public class ExamStudentServiceImpl implements ExamStudentService {
 
             }
         }
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+            sql.append(" and org_id in (" + StringUtils.join(uds.getOrgRule().getRefIds(), ",")+") ");
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+            sql.append(" and course_id in (" + StringUtils.join(uds.getCourseRule().getRefIds(), ",")+") ");
+        }
         return sql;
     }
 
@@ -667,7 +678,10 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public Page<ExamStudentInfo> getReExamineStudentList(ExamStudentQuery query) {
+    public Page<ExamStudentInfo> getReExamineStudentList(UserDataRules uds,ExamStudentQuery query) {
+    	if(uds.getCourseRule().assertEmptyQueryResult()||uds.getOrgRule().assertEmptyQueryResult()) {
+    		return Page.empty();
+    	}
         //获取考试的默认次数
         ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(query.getExamId());
 
@@ -727,6 +741,12 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         if (StringUtils.isNotBlank(query.getCourseLevel())) {
             sql.append(" and t.course_level= '" + query.getCourseLevel() + "'");
         }
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+        	sql.append(" and t.org_id in (" + StringUtils.join(uds.getOrgRule().getRefIds(), ",")+") ");
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+        	sql.append(" and t.course_id in (" + StringUtils.join(uds.getCourseRule().getRefIds(), ",")+") ");
+        }
         int currentNum = (query.getPageNo() - 1) * query.getPageSize();
         sql.append(" limit " + currentNum + "," + query.getPageSize());
 
@@ -754,7 +774,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                 return examStudentEntity;
             }
         });
-        long totalSize = countReExamine(query, examBean);
+        long totalSize = countReExamine(uds,query, examBean);
         List<ExamStudentInfo> list = new ArrayList<ExamStudentInfo>();
         for (ExamStudentEntity examStudentEntity : examStudentList) {
             ExamStudentInfo examStudentInfo = ExamStudentEntityConvert.of(examStudentEntity);
@@ -768,7 +788,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         return new PageImpl<>(list, pageable, totalSize);
     }
 
-    private Long countReExamine(ExamStudentQuery query, ExamSettingsCacheBean examBean) {
+    private Long countReExamine(UserDataRules uds,ExamStudentQuery query, ExamSettingsCacheBean examBean) {
         StringBuffer sql = new StringBuffer();
         sql.append("SELECT count(*)" +
                 " FROM " +
@@ -800,6 +820,12 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         if (StringUtils.isNotBlank(query.getCourseLevel())) {
             sql.append(" and student.course_level= '" + query.getCourseLevel() + "'");
         }
+        if(uds.getOrgRule().assertNeedQueryRefIds()) {
+        	sql.append(" and student.org_id in (" + StringUtils.join(uds.getOrgRule().getRefIds(), ",")+") ");
+        }
+        if(uds.getCourseRule().assertNeedQueryRefIds()) {
+        	sql.append(" and student.course_id in (" + StringUtils.join(uds.getCourseRule().getRefIds(), ",")+") ");
+        }
         return jdbcTemplate.queryForObject(sql.toString(), Long.class);
     }
 
@@ -858,8 +884,11 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(Long examId, Long examStageId, Long orgId) {
-        ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
+    public List<ExamStudentOrgStatistic> getExamStudentStatisticByOrg(UserDataRule ud,Long examId, Long examStageId, Long orgId) {
+    	if(ud.assertEmptyQueryResult()) {
+    		return Lists.newArrayList();
+    	}
+    	ExamSettingsCacheBean examBean = ExamCacheTransferHelper.getDefaultCachedExam(examId);
         if (ExamType.ONLINE.name().equals(examBean.getExamType()) ||
                 ExamType.ONLINE_HOMEWORK.name().equals(examBean.getExamType())) {
             StringBuffer totalsql = new StringBuffer();
@@ -903,7 +932,13 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                     statistic.setFinishedPercent(new DecimalFormat("#.00").format(percent * 100));
                 }
             }
-            return totalList;
+            List<ExamStudentOrgStatistic> ret=Lists.newArrayList();
+            for (ExamStudentOrgStatistic statistic : totalList) {
+            	if(!ud.assertNeedQueryRefIds()||ud.getRefIds().contains(statistic.getOrgId())) {
+            		ret.add(statistic);
+            	}
+            }
+            return ret;
         } else {
             SqlWrapper wrapper = new SqlWrapper()
                     .select(statisticOrgColumns())
@@ -926,7 +961,13 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                 statistic.setOrgCode(orgBean.getCode());
                 statistic.setOrgName(orgBean.getName());
             }
-            return examStudentOrgStatisticList;
+            List<ExamStudentOrgStatistic> ret=Lists.newArrayList();
+            for (ExamStudentOrgStatistic statistic : examStudentOrgStatisticList) {
+            	if(!ud.assertNeedQueryRefIds()||ud.getRefIds().contains(statistic.getOrgId())) {
+            		ret.add(statistic);
+            	}
+            }
+            return ret;
         }
     }
 
@@ -1001,8 +1042,11 @@ public class ExamStudentServiceImpl implements ExamStudentService {
     }
 
     @Override
-    public List<CourseProgressInfo> queryCourseProgressInfos(Long examId, Long examStageId,
+    public List<CourseProgressInfo> queryCourseProgressInfos(UserDataRule ud,Long examId, Long examStageId,
                                                              Long courseId, String orderColumn) {
+    	if(ud.assertEmptyQueryResult()) {
+    		return Lists.newArrayList();
+    	}
         if (examId == null) {
             return null;
         }
@@ -1051,7 +1095,13 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                     statistic.setCompletedProportion(Double.valueOf(new DecimalFormat("#.00").format(percent * 100)));
                 }
             }
-            return totalList;
+            List<CourseProgressInfo> ret=Lists.newArrayList();
+            for (CourseProgressInfo statistic : totalList) {
+            	if(!ud.assertNeedQueryRefIds()||ud.getRefIds().contains(statistic.getCourseId())) {
+            		ret.add(statistic);
+            	}
+            }
+            return ret;
         } else {
             if (StringUtils.isBlank(orderColumn)) {
                 orderColumn = "all_num";
@@ -1073,7 +1123,7 @@ public class ExamStudentServiceImpl implements ExamStudentService {
             }
             sql += " group by course_id ) tb ORDER BY " + orderColumn + " desc";
 
-            return jdbcTemplate.query(sql, new RowMapper<CourseProgressInfo>() {
+            List<CourseProgressInfo> totalList= jdbcTemplate.query(sql, new RowMapper<CourseProgressInfo>() {
                 @Override
                 public CourseProgressInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
                     CourseProgressInfo courseProgressInfo = new CourseProgressInfo();
@@ -1085,6 +1135,13 @@ public class ExamStudentServiceImpl implements ExamStudentService {
                     return courseProgressInfo;
                 }
             });
+            List<CourseProgressInfo> ret=Lists.newArrayList();
+            for (CourseProgressInfo statistic : totalList) {
+            	if(!ud.assertNeedQueryRefIds()||ud.getRefIds().contains(statistic.getCourseId())) {
+            		ret.add(statistic);
+            	}
+            }
+            return ret;
         }
     }
 
@@ -1325,13 +1382,13 @@ public class ExamStudentServiceImpl implements ExamStudentService {
         examStudentInfo.setUsedNum(ret);
     }
 
-    //获取考试的考生信息
-    private ExamStudentBean getRemoteExamStudent(Long rootOrgId, Long examStudentId) {
-        GetExamStudentReq req = new GetExamStudentReq();
-        req.setExamStudentId(examStudentId);
-        req.setRootOrgId(rootOrgId);
-        GetExamStudentResp resp = examStudentCloudService.getExamStudent(req);
-        return resp.getExamStudentBean();
-    }
+//    //获取考试的考生信息
+//    private ExamStudentBean getRemoteExamStudent(Long rootOrgId, Long examStudentId) {
+//        GetExamStudentReq req = new GetExamStudentReq();
+//        req.setExamStudentId(examStudentId);
+//        req.setRootOrgId(rootOrgId);
+//        GetExamStudentResp resp = examStudentCloudService.getExamStudent(req);
+//        return resp.getExamStudentBean();
+//    }
 
 }

+ 10 - 0
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/ExportTaskServiceImpl.java

@@ -23,6 +23,8 @@ import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.examwork.api.request.GetExamMapsReq;
 import cn.com.qmth.examcloud.examwork.api.response.GetExamMapsResp;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+
 import org.apache.commons.collections.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.*;
@@ -70,6 +72,9 @@ public class ExportTaskServiceImpl implements ExportTaskService {
         entity.setExamId(info.getExamId());
         entity.setFilePath(info.getFilePath());
         entity.setCreator(info.getCreator());
+        if(info.getJsonParams()!=null&&info.getJsonParams().length()>980) {
+        	throw new StatusException("输入的导出条件过长!");
+        }
         entity.setExportParam(info.getJsonParams());
         exportTaskRepo.save(entity);
         return entity.getId();
@@ -256,4 +261,9 @@ public class ExportTaskServiceImpl implements ExportTaskService {
         return info;
     }
 
+	@Override
+	public ExportTaskEntity findById(Long taskId) {
+		return GlobalHelper.getEntity(exportTaskRepo, taskId, ExportTaskEntity.class);
+	}
+
 }

+ 134 - 107
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/OfflineExamServiceImpl.java

@@ -3,6 +3,9 @@ package cn.com.qmth.examcloud.core.oe.admin.service.impl;
 import cn.com.qmth.examcloud.api.commons.enums.ExamSpecialSettingsType;
 import cn.com.qmth.examcloud.api.commons.enums.ExamType;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
+import cn.com.qmth.examcloud.core.oe.admin.base.Constants;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordFileAnswerRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamStudentRepo;
@@ -15,9 +18,12 @@ import cn.com.qmth.examcloud.core.oe.admin.service.bean.OfflineExamCourseInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordDataBean;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examrecord.ExamRecordFileAnswerInfo;
 import cn.com.qmth.examcloud.core.oe.admin.service.bean.examstudent.ExamStudentBean;
+import cn.com.qmth.examcloud.core.oe.admin.service.bean.uploadfile.FileInfo;
 import cn.com.qmth.examcloud.core.questions.api.ExtractConfigCloudService;
 import cn.com.qmth.examcloud.core.questions.api.request.GetPaperReq;
 import cn.com.qmth.examcloud.core.questions.api.response.GetPaperResp;
+import cn.com.qmth.examcloud.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.*;
 import cn.com.qmth.examcloud.support.enums.ExamProperties;
@@ -25,8 +31,10 @@ import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.filestorage.FileStoragePathEnvInfo;
 import cn.com.qmth.examcloud.web.filestorage.YunPathInfo;
-import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
-import com.mysql.cj.util.StringUtils;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -34,10 +42,10 @@ import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * @author chenken
@@ -74,6 +82,9 @@ public class OfflineExamServiceImpl implements OfflineExamService {
     @Autowired
     private ExamRecordFileAnswerRepo examRecordFileAnswerRepo;
 
+    @Autowired
+    private ExamStudentService examStudentService;
+
     @Value("${app.upyun.uploadUrl}")
     private String upyunUploadUrl;
 
@@ -164,6 +175,13 @@ public class OfflineExamServiceImpl implements OfflineExamService {
 
     @Override
     public void startOfflineExam(Long examStudentId) {
+        Check.isNull(examStudentId, "examStudentId不能为空");
+
+        boolean isEnable = examStudentService.isEnableExamStudent(examStudentId);
+        if (!isEnable) {
+            throw new StatusException("000500", "当前考生已禁用!");
+        }
+
         SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
         boolean stuClientLoginLimitBoolean = false;
         if (stuClientLoginLimit.getHasValue()) {
@@ -250,9 +268,31 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         return examBean;
     }
 
+    @Override
+    public void submitPaper(Long examRecordDataId, FileInfo fileInfo, Long userId) {
+        if (fileInfo.getFileSize() > Constants.ANSWER_FILE_MAX_SIZE * Constants.M_SIZE) {
+            throw new StatusException("400402", "文件大小不能超过" + Constants.ANSWER_FILE_MAX_SIZE + "M");
+        }
+
+        String fileType;
+        if (".zip".equals(fileInfo.getFileSuffix())) {
+            fileType = "ZIP";
+        } else if (".pdf".equals(fileInfo.getFileSuffix())) {
+            fileType = "PDF";
+        } else if (".jpg.jpeg.png".contains(fileInfo.getFileSuffix())) {
+            fileType = "IMAGE";
+        } else {
+            fileType = "UNKNOWN";
+        }
+
+        this.batchSubmitPaper(examRecordDataId, Lists.newArrayList(fileInfo), fileType, userId);
+    }
+
     @Override
     @Transactional
-    public void submitPaper(Long examRecordDataId, File tempFile, String fileType) throws Exception {
+    public void batchSubmitPaper(Long examRecordDataId, List<FileInfo> fileInfoList, String fileType, Long userId) {
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+
         SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
         Boolean stuClientLoginLimitBoolean = false;
         if (stuClientLoginLimit.getHasValue()) {
@@ -261,105 +301,103 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         if (stuClientLoginLimitBoolean) {
             throw new StatusException("4001", "系统维护中... ...");
         }
-        ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-        if (examRecordDataEntity == null) {
-            return;
+
+        Optional<ExamRecordDataEntity> optional = examRecordDataRepo.findById(examRecordDataId);
+        if (!optional.isPresent()) {
+            throw new StatusException("400403", "考试记录不存在");
         }
-        ExamRecordDataBean bean = of(examRecordDataEntity);
-        String fileName = tempFile.getName();
-        String fileSuffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();
-
-        //上传文件至又拍云
-        ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-        String fileNewName = createOfflineFileName(bean) + "." + fileSuffix;
-        String relativePath = upyunUploadUrl + examRecordData.getExamId() + "/" + fileNewName;
-
-        //通用存储
-        FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
-        env.setRootOrgId(String.valueOf(bean.getRootOrgId()));
-        env.setRelativePath(relativePath);
-        YunPathInfo pi = FileStorageUtil.saveFile("offlineFile", env, tempFile, null);
-        tempFile.delete();
-
-        examRecordForMarkingService.saveOffLineExamRecordForMarking(bean, fileNewName, pi.getRelativePath());
-        if (!StringUtils.isNullOrEmpty(fileType)) {
-            fileType = fileType.toLowerCase();
+
+        ExamRecordDataEntity examRecordData = optional.get();
+        if (ExamType.OFFLINE != examRecordData.getExamType()) {
+            throw new StatusException("400403", "非离线考试");
         }
-        saveExamRecordFileAnswer(examRecordDataId, fileName, fileNewName, pi.getRelativePath(), fileType, fileSuffix);
 
-        //更新考试记录状态
-        examRecordDataEntity.setExamRecordStatus(ExamRecordStatus.EXAM_END);
-        examRecordDataEntity.setEndTime(new Date());//交卷(上传)时间
-        examRecordDataRepo.save(examRecordDataEntity);
-    }
+        if (!examStudentService.isEnableExamStudent(examRecordData.getExamStudentId())) {
+            throw new StatusException("400403", "当前考生已禁用");
+        }
 
-    /**
-     * 上传作答
-     *
-     * @param examRecordDataId 考试记录id
-     * @param tempFiles        文件集合
-     * @param fileType         文件类型
-     */
-    @Override
-    @Transactional
-    public void batchSubmitPaper(Long examRecordDataId, List<File> tempFiles, String fileType) throws Exception {
-        {
-            SysPropertyCacheBean stuClientLoginLimit = CacheHelper.getSysProperty("STU_CLIENT_LOGIN_LIMIT");
-            Boolean stuClientLoginLimitBoolean = false;
-            if (stuClientLoginLimit.getHasValue()) {
-                stuClientLoginLimitBoolean = Boolean.valueOf(stuClientLoginLimit.getValue().toString());
-            }
-            if (stuClientLoginLimitBoolean) {
-                throw new StatusException("4001", "系统维护中... ...");
-            }
-            ExamRecordDataEntity examRecordDataEntity = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-            if (examRecordDataEntity == null) {
-                return;
-            }
-            ExamRecordDataBean bean = of(examRecordDataEntity);
-            examRecordForMarkingService.saveOffLineExamRecordForMarking(bean);
-
-            //删除原文件
-            List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
-            if (null != fileAnswerList && !fileAnswerList.isEmpty()) {
-                for (ExamRecordFileAnswerEntity erfa : fileAnswerList) {
-                    examRecordFileAnswerRepo.deleteById(erfa.getId());
-                    FileStorageUtil.deleteFile(erfa.getFileUrl());
-                }
+        // 当前考试允许的附件类型,示例:["ZIP","PDF","IMAGE"]
+        ExamPropertyCacheBean examProperty = ExamCacheTransferHelper.getCachedExamProperty(examRecordData.getExamId(),
+                examRecordData.getStudentId(), ExamProperties.OFFLINE_UPLOAD_FILE_TYPE.name());
+        String[] settingFileTypes = new JsonMapper().toArray(examProperty.getValue(), String.class);
+        if (ArrayUtils.isEmpty(settingFileTypes)) {
+            throw new StatusException("400403", "当前考试设置不允许上传附件");
+        }
+
+        String uploadFileType = fileType.toUpperCase();
+        if (!ArrayUtils.contains(settingFileTypes, uploadFileType)) {
+            throw new StatusException("400403", "当前考试允许上传文件格式为:" + examProperty.getValue());
+        }
+
+        if (("ZIP".equals(uploadFileType) || "PDF".equals(uploadFileType)) && fileInfoList.size() > 1) {
+            throw new StatusException("400403", "上传" + uploadFileType + "类型文件时只能传一个文件");
+        }
+
+        ExamRecordDataBean bean = of(examRecordData);
+
+        // 上传文件
+        for (FileInfo fileInfo : fileInfoList) {
+            if (!matchFileTypes(new String[]{uploadFileType}, fileInfo.getFileSuffix())) {
+                throw new StatusException("400401", "请上传【" + uploadFileType + "】类型的文件");
             }
 
-            //保存新上传的文件
-            for (File tempFile : tempFiles) {
-                String fileName = tempFile.getName();
-                String fileSuffix = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length()).toLowerCase();
-                //上传文件至又拍云
-                ExamRecordDataEntity examRecordData = GlobalHelper.getEntity(examRecordDataRepo, examRecordDataId, ExamRecordDataEntity.class);
-                String fileNewName = createOfflineFileName(bean) + "." + fileSuffix;
-                String relativePath = upyunUploadUrl + examRecordData.getExamId() + "/" + fileNewName;
-
-                //通用存储
-                FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
-                env.setRootOrgId(String.valueOf(bean.getRootOrgId()));
-                env.setRelativePath(relativePath);
-                YunPathInfo pi = FileStorageUtil.saveFile("offlineFile", env, tempFile, null);
-                //                tempFile.delete();
-
-                //保存离线文件至数据库
-                ExamRecordFileAnswerEntity entity = new ExamRecordFileAnswerEntity();
-                entity.setExamRecordDataId(examRecordDataId);
-                entity.setOriginalFileName(fileName);
-                entity.setFileName(fileNewName);
-                entity.setFileUrl(pi.getRelativePath());
-                entity.setFileType(fileType.toLowerCase());
-                entity.setSuffix(fileSuffix);
-                examRecordFileAnswerRepo.save(entity);
+            String fileNewName = createOfflineFileName(bean) + fileInfo.getFileSuffix();
+            String relativePath = upyunUploadUrl + examRecordData.getExamId() + "/" + fileNewName;
+
+            FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+            env.setRootOrgId(String.valueOf(examRecordData.getRootOrgId()));
+            env.setRelativePath(relativePath);
+            YunPathInfo uploadResult = FileStorageUtil.saveFile("offlineFile", env, fileInfo.getFileBytes(), false);
+
+            fileInfo.setFilePath(uploadResult.getRelativePath());
+            fileInfo.setFileName(fileNewName);
+            fileInfo.setFileType(fileType);
+        }
+
+        // 删除原作答文件记录
+        List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
+        if (CollectionUtils.isNotEmpty(fileAnswerList)) {
+            for (ExamRecordFileAnswerEntity fileAnswer : fileAnswerList) {
+                examRecordFileAnswerRepo.delete(fileAnswer);
+                FileStorageUtil.deleteFile(fileAnswer.getFileUrl());
             }
+        }
+
+        // 保存新作答文件记录
+        for (FileInfo fileInfo : fileInfoList) {
+            saveExamRecordFileAnswer(examRecordDataId, fileInfo.getOriginalFileName(), fileInfo.getFileName(), fileInfo.getFilePath(),
+                    fileInfo.getFileType().toLowerCase(), fileInfo.getFileSuffix().replaceFirst(".", ""));
+        }
+
+        fileInfoList.clear();
 
-            //更新考试记录状态
-            examRecordDataEntity.setExamRecordStatus(ExamRecordStatus.EXAM_END);
-            examRecordDataEntity.setEndTime(new Date());//交卷(上传)时间
-            examRecordDataRepo.save(examRecordDataEntity);
+        examRecordForMarkingService.saveOffLineExamRecordForMarking(bean);
+
+        //更新考试记录状态,交卷(上传)时间
+        examRecordDataRepo.updateExamRecordStatusById(examRecordDataId, ExamRecordStatus.EXAM_END, new Date());
+
+        ReportsUtil.report(new AdminOperateReport(examRecordData.getRootOrgId(), userId, "考试进度详情-上传作答",
+                "考试记录ID:" + examRecordDataId + " 考生ID:" + examRecordData.getExamStudentId()));
+    }
+
+    private boolean matchFileTypes(String[] settingFileTypes, String fileSuffix) {
+        if (ArrayUtils.isEmpty(settingFileTypes) || StringUtils.isEmpty(fileSuffix)) {
+            return false;
+        }
+
+        for (String fileType : settingFileTypes) {
+            if ("IMAGE".equals(fileType)) {
+                if (".jpg.jpeg.png".contains(fileSuffix)) {
+                    return true;
+                }
+            } else {
+                // ZIP、PDF
+                if (("." + fileType).equalsIgnoreCase(fileSuffix)) {
+                    return true;
+                }
+            }
         }
+        return false;
     }
 
     /**
@@ -372,18 +410,8 @@ public class OfflineExamServiceImpl implements OfflineExamService {
      * @param fileType
      * @param fileSuffix
      */
-    private void saveExamRecordFileAnswer(Long examRecordDataId, String originalFileName,
-                                          String fileName, String relativePath,
-                                          String fileType, String fileSuffix) {
-        //删除原文件
-        List<ExamRecordFileAnswerEntity> fileAnswerList = examRecordFileAnswerRepo.findByExamRecordDataId(examRecordDataId);
-        if (null != fileAnswerList && !fileAnswerList.isEmpty()) {
-            for (ExamRecordFileAnswerEntity erfa : fileAnswerList) {
-                examRecordFileAnswerRepo.deleteById(erfa.getId());
-                FileStorageUtil.deleteFile(erfa.getFileUrl());
-            }
-        }
-
+    private void saveExamRecordFileAnswer(Long examRecordDataId, String originalFileName, String fileName,
+                                          String relativePath, String fileType, String fileSuffix) {
         ExamRecordFileAnswerEntity entity = new ExamRecordFileAnswerEntity();
         entity.setExamRecordDataId(examRecordDataId);
         entity.setOriginalFileName(originalFileName);
@@ -394,7 +422,6 @@ public class OfflineExamServiceImpl implements OfflineExamService {
         examRecordFileAnswerRepo.save(entity);
     }
 
-
     private String createOfflineFileName(ExamRecordDataBean examRecordData) {
         long currentTime = System.currentTimeMillis();
 

+ 9 - 11
examcloud-core-oe-admin-service/src/main/java/cn/com/qmth/examcloud/core/oe/admin/service/impl/PracticeServiceImpl.java

@@ -11,6 +11,7 @@ import org.springframework.stereotype.Service;
 import com.alibaba.fastjson.JSONObject;
 
 import cn.com.qmth.examcloud.commons.util.StringUtil;
+import cn.com.qmth.examcloud.core.oe.admin.base.utils.Check;
 import cn.com.qmth.examcloud.core.oe.admin.base.utils.QuestionTypeUtil;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamRecordDataRepo;
 import cn.com.qmth.examcloud.core.oe.admin.dao.ExamScoreRepo;
@@ -39,7 +40,6 @@ import cn.com.qmth.examcloud.support.enums.ExamProperties;
 import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 
-
 /**
  * @author chenken
  * @date 2018年9月7日 上午11:05:18
@@ -66,9 +66,9 @@ public class PracticeServiceImpl implements PracticeService {
 
     @Override
     public List<PracticeCourseInfo> queryPracticeCourseList(Long examId, Long studentId) {
-        if (examId == null || studentId == null) {
-            return null;
-        }
+        Check.isNull(examId, "examId不能为空");
+        Check.isNull(studentId, "studentId不能为空");
+
         List<ExamStudentEntity> examStudentList = examStudentRepo.findByExamIdAndStudentId(examId, studentId);
         if (examStudentList == null || examStudentList.size() == 0) {
             return null;
@@ -165,9 +165,8 @@ public class PracticeServiceImpl implements PracticeService {
 
     @Override
     public List<PracticeRecordInfo> queryPracticeRecordList(Long examStudentId) {
-        if (examStudentId == null) {
-            return null;
-        }
+        Check.isNull(examStudentId, "examStudentId不能为空");
+
         List<ExamRecordDataEntity> examRecordDataList = examRecordDataRepo.findByExamStudentId(examStudentId);
         List<PracticeRecordInfo> practiceRecords = new ArrayList<PracticeRecordInfo>();
         ExamStudentEntity examStudent = examStudentRepo.findByExamStudentId(examStudentId);
@@ -247,14 +246,13 @@ public class PracticeServiceImpl implements PracticeService {
 
     @Override
     public PracticeDetailInfo getPracticeDetailInfo(Long examRecordDataId) {
-        if (examRecordDataId == null) {
-            return null;
-        }
+        Check.isNull(examRecordDataId, "examRecordDataId不能为空");
+
         PracticeDetailInfo practiceDetailInfo = new PracticeDetailInfo();
         //取出试卷结构
         ExamRecordPaperStructEntity examRecordPaperStruct = examRecordPaperStructService.getExamRecordPaperStruct(examRecordDataId);
         DefaultPaper defaultPaper = examRecordPaperStruct.getDefaultPaper();
-        List<PaperStructInfo> paperStructInfos = new ArrayList<PaperStructInfo>();
+        List<PaperStructInfo> paperStructInfos = new ArrayList<>();
         List<DefaultQuestionGroup> questionGroupList = defaultPaper.getQuestionGroupList();
         //取出作答记录
         ExamRecordQuestionsEntity examRecordQuestions =

+ 5 - 5
examcloud-core-oe-admin-starter/shell/start.sh

@@ -1,17 +1,17 @@
 #!/bin/bash
 
-PROJECT_JAR="examcloud-core-oe-admin-starter-v4.1.0-RELEASE.jar"
-
-PROJECT_JVM_ARGS=`cat start.vmoptions`
+PROJECT_JAR=`find . -name "examcloud-core-oe-admin-starter*.jar"`
+PROJECT_JAR=${PROJECT_JAR:6}
 
 PROJECT_ARGS=`cat start.args`
-
 PROJECT_ARGS=$PROJECT_ARGS" --sys.config.center.secretKey="$1
 
+PROJECT_JVM_ARGS=`cat start.vmoptions`
+
 PID_LIST=`ps -ef | grep $PROJECT_JAR | grep java | awk '{print $2}'`
 if [ ! -z "$PID_LIST" ]; then
     echo "$PROJECT_JAR is already running..."
-    exit -1
+    #exit -1
 fi
 
 nohup java $PROJECT_JVM_ARGS -jar lib/$PROJECT_JAR $PROJECT_ARGS >/dev/null 2>&1 &

+ 2 - 1
examcloud-core-oe-admin-starter/shell/stop.sh

@@ -1,6 +1,7 @@
 #!/bin/bash
 
-PROJECT_JAR="examcloud-core-oe-admin-starter-v4.1.0-RELEASE.jar"
+PROJECT_JAR=`find . -name "examcloud-core-oe-admin-starter*.jar"`
+PROJECT_JAR=${PROJECT_JAR:6}
 
 ps -ef | grep $PROJECT_JAR | grep java | awk '{printf("kill -15 %s\n",$2)}' | sh
 BUILD_ID=DONTKILLME

+ 17 - 13
examcloud-core-oe-admin-starter/src/main/java/cn/com/qmth/examcloud/core/oe/admin/stater/config/SwaggerConfig.java

@@ -1,44 +1,48 @@
-/*
- * *************************************************
- * Copyright (c) 2018 QMTH. All Rights Reserved.
- * Created by Deason on 2018-08-15 16:17:41.
- * *************************************************
- */
-
 package cn.com.qmth.examcloud.core.oe.admin.stater.config;
 
-import io.swagger.annotations.ApiOperation;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.ParameterBuilder;
 import springfox.documentation.builders.PathSelectors;
 import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.schema.ModelRef;
 import springfox.documentation.service.ApiInfo;
+import springfox.documentation.service.Parameter;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spring.web.plugins.Docket;
 import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @Configuration
 @EnableSwagger2WebMvc
 public class SwaggerConfig {
 
     @Bean
     public Docket buildDocket() {
+        List<Parameter> parameters = new ArrayList<>();
+        parameters.add(new ParameterBuilder().name("key").modelRef(new ModelRef("String")).parameterType("header").required(false).build());
+        parameters.add(new ParameterBuilder().name("token").modelRef(new ModelRef("String")).parameterType("header").required(false).build());
+
         return new Docket(DocumentationType.SWAGGER_2)
-                .groupName("Version 3.0")
+                .groupName("default")
                 .apiInfo(buildApiInfo())
+                .globalOperationParameters(parameters)
                 .useDefaultResponseMessages(false)
                 .select()
-                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                // .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                .apis(RequestHandlerSelectors.basePackage("cn.com.qmth.examcloud.core.oe.admin.api.controller.client"))
                 .paths(PathSelectors.any())
                 .build();
     }
 
     public ApiInfo buildApiInfo() {
         return new ApiInfoBuilder()
-                .title("网考管理端接口文档")
-                .version("3.0")
+                .title("考试云平台接口文档")
+                .version("v4.x.x")
                 .build();
     }
 
-}
+}

+ 18 - 0
examcloud-core-oe-admin-starter/src/test/java/cn/com/qmth/examcloud/core/oe/admin/test/OeAdminTest.java

@@ -0,0 +1,18 @@
+package cn.com.qmth.examcloud.core.oe.admin.test;
+
+import cn.com.qmth.examcloud.core.oe.admin.stater.OEAdminApp;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = OEAdminApp.class)
+public class OeAdminTest {
+
+    @Test
+    public void demo() throws Exception {
+
+    }
+
+}