deason 3 éve
szülő
commit
6a6deeeeef

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

@@ -3,8 +3,12 @@ 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.commons.util.JsonMapper;
 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;
@@ -12,19 +16,25 @@ 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.reports.commons.bean.AdminOperateReport;
+import cn.com.qmth.examcloud.reports.commons.util.ReportsUtil;
+import cn.com.qmth.examcloud.support.cache.bean.ExamPropertyCacheBean;
+import cn.com.qmth.examcloud.support.enums.ExamProperties;
+import cn.com.qmth.examcloud.support.helper.ExamCacheTransferHelper;
+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.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
+import java.util.Optional;
 
 @Api(tags = "(客户端)考试过程相关接口")
 @RestController
@@ -161,18 +171,79 @@ public class ExamProcessController extends ControllerSupport {
 
     @ApiOperation(value = "离线考试:交卷")
     @PostMapping("/submitPaper")
-    public void submitPaper(@RequestParam(value = "file") MultipartFile file,
-                            @RequestParam long examRecordDataId) throws Exception {
+    public void submitPaper(@RequestPart(value = "file") MultipartFile file,
+                            @RequestParam Long examRecordDataId) throws Exception {
+        Check.isNull(file, "上传文件不能为空");
 
+        String fileSuffix = FileUtil.getFileSuffix(file.getOriginalFilename());
+        if (!FileUtil.checkFileSuffix("jpg|jpeg|png|pdf|zip", fileSuffix)) {
+            throw new StatusException("400401", "文件格式不正确");
+        }
+
+        if (file.getSize() > Constants.ANSWER_FILE_MAX_SIZE * Constants.M_SIZE) {
+            throw new StatusException("400402", "文件大小不能超过" + Constants.ANSWER_FILE_MAX_SIZE + "M");
+        }
+
+        Optional<ExamRecordDataEntity> optional = examRecordDataRepo.findById(examRecordDataId);
+        if (!optional.isPresent()) {
+            throw new StatusException("000500", "考试记录不存在");
+        }
+
+        ExamRecordDataEntity examRecordData = optional.get();
+        if (ExamType.OFFLINE != examRecordData.getExamType()) {
+            throw new StatusException("000500", "非离线考试");
+        }
+
+        if (!examStudentService.isEnableExamStudent(examRecordData.getExamStudentId())) {
+            throw new StatusException("000500", "当前考生已禁用");
+        }
+
+        // 当前考试允许的附件类型,示例:["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("000500", "当前考试设置不允许上传附件");
+        }
+        if (!matchFileTypes(settingFileTypes, fileSuffix)) {
+            throw new StatusException("000500", "当前考试允许上传文件格式为:" + examProperty.getValue());
+        }
+
+        // todo
+        // offlineExamService.submitPaper(examRecordDataId, file.getInputStream(), fileSuffix);
+
+        User user = getAccessUser();
+        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), "考试进度详情-上传作答",
+                "考试记录ID:" + examRecordDataId + " 考生ID:" + examRecordData.getExamStudentId()));
     }
 
     @ApiOperation(value = "离线考试:多文件交卷")
     @PostMapping("/batchSubmitPaper")
-    public void batchSubmitPaper(@ApiParam(value = "文件数组") @RequestParam MultipartFile[] fileArray,
-                                 @ApiParam(value = "考试记录id") @RequestParam long examRecordDataId,
+    public void batchSubmitPaper(@ApiParam(value = "文件数组") @RequestPart MultipartFile[] fileArray,
+                                 @ApiParam(value = "考试记录id") @RequestParam Long examRecordDataId,
                                  @ApiParam(value = "文件类型:ZIP/PDF/IMAGE") @RequestParam String fileType,
                                  @ApiParam(value = "md5加密的文件摘要") @RequestParam String[] fileMd5Array) throws Exception {
 
     }
 
+    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;
+    }
+
 }

+ 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;
+
+}