瀏覽代碼

试评功能阶段保存版本

luoshi 6 年之前
父節點
當前提交
09fb4c5af6
共有 42 個文件被更改,包括 2478 次插入712 次删除
  1. 4 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/ExamStudentDao.java
  2. 41 18
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/MarkGroupDao.java
  3. 11 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/ExamQuestion.java
  4. 37 1
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/MarkGroup.java
  5. 2 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/ExamStudentService.java
  6. 12 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/MarkGroupService.java
  7. 14 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/impl/ExamStudentServiceImpl.java
  8. 29 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/impl/MarkGroupServiceImpl.java
  9. 36 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/TrialHistoryDao.java
  10. 47 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/TrialLibraryDao.java
  11. 35 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/TrialTrackDao.java
  12. 168 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/MarkResult.java
  13. 1 161
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/Task.java
  14. 17 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrackDTO.java
  15. 158 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialHistory.java
  16. 66 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialHistoryPK.java
  17. 133 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialLibrary.java
  18. 143 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialTrack.java
  19. 101 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialTrackPK.java
  20. 69 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/query/TrialLibrarySearchQuery.java
  21. 76 52
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkCronService.java
  22. 1 1
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkLockService.java
  23. 280 119
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkServiceImpl.java
  24. 76 4
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/TaskServiceImpl.java
  25. 128 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/TrialServiceImpl.java
  26. 56 24
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/MarkService.java
  27. 4 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/TaskService.java
  28. 33 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/TrialService.java
  29. 206 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/utils/TrialTaskUtil.java
  30. 0 34
      stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/MarkStage.java
  31. 32 0
      stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/MarkStatus.java
  32. 157 157
      stmms-common/src/main/java/cn/com/qmth/stmms/common/redis/RedisKeyBuilder.java
  33. 2 1
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/SubjectQuestionDTO.java
  34. 13 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/SubjectiveQuestionDTO.java
  35. 41 5
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkGroupController.java
  36. 26 0
      stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/BaseController.java
  37. 111 67
      stmms-web/src/main/java/cn/com/qmth/stmms/mark/MarkController.java
  38. 7 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupAdd.jsp
  39. 9 1
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupEditSimple.jsp
  40. 16 8
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupList.jsp
  41. 6 4
      stmms-web/src/main/webapp/static/mark-new/js/mark-control.js
  42. 74 55
      stmms-web/src/main/webapp/static/mark-new/js/modules/mark-status.js

+ 4 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/ExamStudentDao.java

@@ -120,6 +120,10 @@ public interface ExamStudentDao
     public List<ExamStudent> findUnLibraryStudent(Integer examId, String subjectCode, Integer groupNumber,
             Date minUploadTime, Pageable page);
 
+    @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and s.upload=true and s.absent=false and s.breach=false "
+            + "and not exists (select l.id from TrialLibrary l where l.studentId=s.id and l.groupNumber=?3)")
+    public List<ExamStudent> findUnTrialStudent(Integer examId, String subjectCode, Integer groupNumber, Pageable page);
+
     @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and (s.absent=true or s.breach=true) and "
             + "exists (select l.id from MarkLibrary l where l.studentId=s.id)")
     public List<ExamStudent> findAbsentOrBreachLibraryStudent(Integer examId, String subjectCode);

+ 41 - 18
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/MarkGroupDao.java

@@ -11,6 +11,7 @@ import org.springframework.data.repository.PagingAndSortingRepository;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroupPK;
 import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.ScorePolicy;
 
 public interface MarkGroupDao
@@ -21,27 +22,38 @@ public interface MarkGroupDao
 
     @Modifying
     @Query("delete from MarkGroup q where q.pk.examId=?1")
-    void deleteByExamId(int examId);
+    void deleteByExamId(Integer examId);
 
     @Modifying
     @Query("delete from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2")
-    void deleteByExamIdAndSubjectCode(int examId, String subjectCode);
+    void deleteByExamIdAndSubjectCode(Integer examId, String subjectCode);
 
     @Query("select count(q) from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2")
-    long countByExamIdAndSubjectCode(int examId, String subjectCode);
+    long countByExamIdAndSubjectCode(Integer examId, String subjectCode);
 
     @Query("select q from MarkGroup q where q.pk.examId=?1 order by q.pk.number")
-    List<MarkGroup> findByExamId(int examId);
+    List<MarkGroup> findByExamId(Integer examId);
 
     @Query("select q from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2 order by q.pk.number")
-    List<MarkGroup> findByExamIdAndSubjectCode(int examId, String subjectCode);
+    List<MarkGroup> findByExamIdAndSubjectCode(Integer examId, String subjectCode);
+
+    @Query("select q from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2 "
+            + "and q.status in (?3) order by q.pk.number")
+    List<MarkGroup> findByExamIdAndSubjectCodeAndStatus(Integer examId, String subjectCode, MarkStatus... status);
 
     @Query("select q from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2 "
             + "and q.doubleRate!=null and q.doubleRate>0 order by q.pk.number")
-    List<MarkGroup> findByExamIdAndSubjectCodeWithDouble(int examId, String subjectCode);
+    List<MarkGroup> findByExamIdAndSubjectCodeWithDouble(Integer examId, String subjectCode);
+
+    @Query("select q.examId from MarkGroup q where q.status in (?1)")
+    List<Integer> findExamIdByStatus(MarkStatus... status);
 
     @Query("select count(q) from MarkGroup q where q.pk.examId=?1")
-    long countByExamId(int examId);
+    long countByExamId(Integer examId);
+
+    @Query("select count(q) from MarkGroup q where q.pk.examId=?1 and q.pk.subjectCode=?2 and q.pk.number=?3 "
+            + "and q.status=?4 and q.trialCount>q.libraryCount")
+    long countByStatusAndTrailCount(Integer examId, String subjectCode, Integer number, MarkStatus status);
 
     @Query("select sum(g.totalScore) from MarkGroup g where g.pk.examId=?1 and g.pk.subjectCode=?2")
     Double sumTotalScore(Integer examId, String subjectCode);
@@ -54,47 +66,58 @@ public interface MarkGroupDao
 
     @Modifying
     @Query("update MarkGroup g set g.libraryCount=?4, g.markedCount=?5, g.leftCount=(?4-?5) where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateLibraryCount(int examId, String subjectCode, int number, int totalCount, int markedCount);
+    void updateLibraryCount(Integer examId, String subjectCode, Integer number, Integer totalCount,
+            Integer markedCount);
 
     @Modifying
     @Query("update MarkGroup g set g.leftCount = g.libraryCount, g.markedCount = 0 "
             + "where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void resetCount(int examId, String subjectCode, int number);
+    void resetCount(Integer examId, String subjectCode, Integer number);
 
     @Modifying
     @Query("update MarkGroup g set g.picList=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updatePicList(int examId, String subjectCode, int number, String picList);
+    void updatePicList(Integer examId, String subjectCode, Integer number, String picList);
 
     @Modifying
     @Query("update MarkGroup g set g.title=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateTitle(int examId, String subjectCode, int number, String title);
+    void updateTitle(Integer examId, String subjectCode, Integer number, String title);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.totalScore=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateTotalScore(int examId, String subjectCode, int number, Double score);
+    void updateTotalScore(Integer examId, String subjectCode, Integer number, Double score);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.scorePolicy=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateScorePolicy(int examId, String subjectCode, int number, ScorePolicy policy);
+    void updateScorePolicy(Integer examId, String subjectCode, Integer number, ScorePolicy policy);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.buildTime=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateBuildTime(int examId, String subjectCode, int number, Date time);
+    void updateBuildTime(Integer examId, String subjectCode, Integer number, Date time);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.buildTime=?3 where g.pk.examId=?1 and g.pk.subjectCode=?2")
-    void updateBuildTime(int examId, String subjectCode, Date time);
+    void updateBuildTime(Integer examId, String subjectCode, Date time);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.doubleRate=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateDoubleRate(int examId, String subjectCode, int number, Double doubleRate);
+    void updateDoubleRate(Integer examId, String subjectCode, Integer number, Double doubleRate);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.markMode=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateMarkMode(int examId, String subjectCode, int number, MarkMode markMode);
+    void updateMarkMode(Integer examId, String subjectCode, Integer number, MarkMode markMode);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkGroup g set g.arbitrateThreshold=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
-    void updateArbitrateThreshold(int examId, String subjectCode, int number, Double arbitrateThreshold);
+    void updateArbitrateThreshold(Integer examId, String subjectCode, Integer number, Double arbitrateThreshold);
+
+    @Modifying(clearAutomatically = true)
+    @Query("update MarkGroup g set g.trialCount=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3")
+    void updateTrialCount(Integer examId, String subjectCode, Integer number, Integer trialCount);
+
+    @Modifying(clearAutomatically = true)
+    @Query("update MarkGroup g set g.status=?4 where g.pk.examId=?1 and g.pk.subjectCode=?2 and g.pk.number=?3 "
+            + "and g.status in (?5)")
+    Integer updateStatus(Integer examId, String subjectCode, Integer number, MarkStatus newStatus,
+            MarkStatus... currentStatus);
 
 }

+ 11 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/ExamQuestion.java

@@ -67,6 +67,9 @@ public class ExamQuestion implements Serializable {
     @Transient
     private String markMode;
 
+    @Transient
+    private Integer trialCount;
+
     public ExamQuestion() {
         this.pk = new QuestionPK();
     }
@@ -255,4 +258,12 @@ public class ExamQuestion implements Serializable {
         this.markMode = markMode;
     }
 
+    public Integer getTrialCount() {
+        return trialCount;
+    }
+
+    public void setTrialCount(Integer trialCount) {
+        this.trialCount = trialCount;
+    }
+
 }

+ 37 - 1
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/MarkGroup.java

@@ -20,6 +20,7 @@ import org.apache.commons.lang.StringUtils;
 import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.biz.utils.ScoreItem;
 import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.ScorePolicy;
 
 @Entity
@@ -40,6 +41,12 @@ public class MarkGroup implements Serializable {
     @Column(name = "total_score", nullable = false)
     private Double totalScore;
 
+    /**
+     * 试评数量
+     */
+    @Column(name = "trial_count", nullable = true)
+    private Integer trialCount;
+
     /**
      * 双评比例(0~1)
      */
@@ -66,6 +73,13 @@ public class MarkGroup implements Serializable {
     @Enumerated(EnumType.STRING)
     private MarkMode markMode;
 
+    /**
+     * 当前评卷状态
+     */
+    @Column(name = "status", length = 16, nullable = false)
+    @Enumerated(EnumType.STRING)
+    private MarkStatus status;
+
     @Temporal(TemporalType.TIMESTAMP)
     @Column(name = "build_time", nullable = true)
     private Date buildTime;
@@ -106,7 +120,7 @@ public class MarkGroup implements Serializable {
 
     public MarkGroup(Integer examId, String subjectCode, Integer number, String title,
             List<PictureConfigItem> configList, Double totalScore, Double doubleRate, Double arbitrateThreshold,
-            Integer scorePolicy, String markMode) {
+            Integer scorePolicy, String markMode, Integer trialCount) {
         this.pk = new MarkGroupPK();
         this.pk.setExamId(examId);
         this.pk.setNumber(number);
@@ -132,6 +146,12 @@ public class MarkGroup implements Serializable {
         if (markMode != null) {
             this.markMode = MarkMode.findByName(markMode);
         }
+        if (trialCount != null && trialCount > 0) {
+            this.status = MarkStatus.TRIAL;
+            this.trialCount = trialCount;
+        } else {
+            this.status = MarkStatus.FORMAL;
+        }
     }
 
     public Integer getExamId() {
@@ -324,4 +344,20 @@ public class MarkGroup implements Serializable {
         this.markMode = markMode;
     }
 
+    public MarkStatus getStatus() {
+        return status;
+    }
+
+    public void setStatus(MarkStatus status) {
+        this.status = status;
+    }
+
+    public Integer getTrialCount() {
+        return trialCount;
+    }
+
+    public void setTrialCount(Integer trialCount) {
+        this.trialCount = trialCount;
+    }
+
 }

+ 2 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/ExamStudentService.java

@@ -124,4 +124,6 @@ public interface ExamStudentService {
 
     long countUploadedByExamId(Integer examId);
 
+    ExamStudent randomUnTrialStudent(Integer examId, String subjectCode, Integer groupNumber);
+
 }

+ 12 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/MarkGroupService.java

@@ -6,6 +6,7 @@ import java.util.List;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 
 public interface MarkGroupService {
 
@@ -37,10 +38,21 @@ public interface MarkGroupService {
 
     void updateMarkMode(int examId, String subjectCode, Integer number, MarkMode markMode);
 
+    void updateTrialCount(int examId, String subjectCode, Integer number, Integer trialCount);
+
     long sumLibraryCount(Integer examId);
 
     long sumMarkedCount(Integer examId);
 
     double sumTotalScore(Integer examId, String subjectCode);
 
+    boolean updateStatus(int examId, String subjectCode, Integer number, MarkStatus newStatus,
+            MarkStatus... currentStatus);
+
+    List<Integer> findExamIdByStatus(MarkStatus... status);
+
+    List<MarkGroup> findByExamAndSubjectAndStatus(Integer examId, String subjectCode, MarkStatus... status);
+
+    boolean needTrialLibrary(Integer examId, String subjectCode, Integer number);
+
 }

+ 14 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/impl/ExamStudentServiceImpl.java

@@ -17,6 +17,7 @@ import javax.persistence.criteria.Predicate;
 import javax.persistence.criteria.Root;
 
 import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.RandomUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Sort;
@@ -646,6 +647,19 @@ public class ExamStudentServiceImpl extends BaseQueryService<ExamStudent> implem
         return list.isEmpty() ? null : list.get(0);
     }
 
+    @Override
+    public ExamStudent randomUnTrialStudent(Integer examId, String subjectCode, Integer groupNumber) {
+        ExamStudentSearchQuery query = new ExamStudentSearchQuery();
+        query.setPageNumber(1);
+        query.setPageSize(10);
+        query.setSort(new Sort(Direction.ASC, "uploadTime", "id"));
+        List<ExamStudent> list = studentDao.findUnTrialStudent(examId, subjectCode, groupNumber, query);
+        if (list.isEmpty()) {
+            return null;
+        }
+        return list.get(RandomUtils.nextInt(list.size()));
+    }
+
     @Override
     public List<ExamStudent> findAbsentOrBreachLibraryStudent(int examId, String subjectCode) {
         return studentDao.findAbsentOrBreachLibraryStudent(examId, subjectCode);

+ 29 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/impl/MarkGroupServiceImpl.java

@@ -16,6 +16,7 @@ import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 
 @Service("markGroupService")
 public class MarkGroupServiceImpl extends BaseQueryService<MarkGroup> implements MarkGroupService {
@@ -78,6 +79,24 @@ public class MarkGroupServiceImpl extends BaseQueryService<MarkGroup> implements
         groupDao.updateArbitrateThreshold(examId, subjectCode, number, arbitrateThreshold);
     }
 
+    @Transactional
+    @Override
+    public void updateTrialCount(int examId, String subjectCode, Integer number, Integer trialCount) {
+        groupDao.updateTrialCount(examId, subjectCode, number, trialCount);
+    }
+
+    @Transactional
+    @Override
+    public boolean updateStatus(int examId, String subjectCode, Integer number, MarkStatus newStatus,
+            MarkStatus... currentStatus) {
+        return groupDao.updateStatus(examId, subjectCode, number, newStatus, currentStatus) > 0;
+    }
+
+    @Override
+    public boolean needTrialLibrary(Integer examId, String subjectCode, Integer number) {
+        return groupDao.countByStatusAndTrailCount(examId, subjectCode, number, MarkStatus.TRIAL) > 0;
+    }
+
     @Override
     public MarkGroup findOne(int examId, String subjectCode, int number) {
         MarkGroupPK pk = new MarkGroupPK();
@@ -102,11 +121,21 @@ public class MarkGroupServiceImpl extends BaseQueryService<MarkGroup> implements
         return groupDao.findByExamIdAndSubjectCode(examId, code);
     }
 
+    @Override
+    public List<MarkGroup> findByExamAndSubjectAndStatus(Integer examId, String subjectCode, MarkStatus... status) {
+        return groupDao.findByExamIdAndSubjectCodeAndStatus(examId, subjectCode, status);
+    }
+
     @Override
     public List<MarkGroup> findByExamAndSubjectWithDouble(int examId, String code) {
         return groupDao.findByExamIdAndSubjectCodeWithDouble(examId, code);
     }
 
+    @Override
+    public List<Integer> findExamIdByStatus(MarkStatus... status) {
+        return groupDao.findExamIdByStatus(status);
+    }
+
     @Override
     public long countByExam(int examId) {
         return groupDao.countByExamId(examId);

+ 36 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/TrialHistoryDao.java

@@ -0,0 +1,36 @@
+package cn.com.qmth.stmms.biz.mark.dao;
+
+import java.util.List;
+
+import org.springframework.data.domain.Pageable;
+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 cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+
+public interface TrialHistoryDao extends JpaRepository<TrialHistory, Integer>, JpaSpecificationExecutor<TrialHistory> {
+
+    List<TrialHistory> findByPkLibraryId(Integer libraryId);
+
+    TrialHistory findByPkLibraryIdAndPkMarkerId(Integer libraryId, Integer markerId);
+
+    List<TrialHistory> findByExamIdAndSubjectCodeAndGroupNumberAndMarkerId(Integer examId, String subjectCode,
+            Integer groupNumber, Integer markerId, Pageable page);
+
+    @Query("select count(*) from TrialHistory f where f.pk.libraryId=?1")
+    long countByLibraryId(Integer libraryId);
+
+    @Query("select count(*) from TrialHistory f where f.pk.markerId=?1")
+    long countByMarkerId(Integer markerId);
+
+    @Modifying
+    @Query("delete TrialHistory m where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3")
+    void deleteByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber);
+
+    @Modifying
+    @Query("delete TrialHistory m where m.studentId=?1")
+    void deleteByStudentId(Integer studentId);
+
+}

+ 47 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/TrialLibraryDao.java

@@ -0,0 +1,47 @@
+package cn.com.qmth.stmms.biz.mark.dao;
+
+import java.util.List;
+
+import org.springframework.data.domain.Pageable;
+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 cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
+
+public interface TrialLibraryDao extends JpaRepository<TrialLibrary, Integer>, JpaSpecificationExecutor<TrialLibrary> {
+
+    List<TrialLibrary> findByExamIdAndSubjectCode(Integer examId, String subjectCode, Pageable page);
+
+    List<TrialLibrary> findByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber,
+            Pageable page);
+
+    @Query("select l1 from TrialLibrary l1 where l1.examId=?1 and l1.subjectCode=?2 and l1.groupNumber=?3 "
+            + "and not exists (select l2 from TrialHistory l2 where l2.libraryId=l1.id and l2.markerId=?4)")
+    List<TrialLibrary> findUnMarked(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
+            Pageable page);
+
+    @Query("select l1 from TrialLibrary l where l.examId=?1 and l.subjectCode=?2 and l.groupNumber=?3 and l.markCount>0")
+    long countMarked(Integer examId, String subjectCode, Integer groupNumber);
+
+    @Query("select l from TrialLibrary l where l.studentId=?1 order by l.groupNumber ")
+    List<TrialLibrary> findByStudentId(Integer studentId);
+
+    List<TrialLibrary> findByStudentIdAndGroupNumber(Integer studentId, Integer groupNumber);
+
+    @Query("select count(*) from TrialLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3")
+    long countByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber);
+
+    @Query("select count(*) from TrialLibrary f where f.studentId=?1 and f.groupNumber=?2")
+    long countByStudentIdAndGroupNumber(Integer studentId, Integer groupNumber);
+
+    @Modifying
+    @Query("delete TrialLibrary m where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3")
+    void deleteByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber);
+
+    @Modifying
+    @Query("delete TrialLibrary m where m.studentId=?1")
+    void deleteByStudentId(Integer studentId);
+
+}

+ 35 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/TrialTrackDao.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.stmms.biz.mark.dao;
+
+import java.util.List;
+
+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 cn.com.qmth.stmms.biz.mark.model.TrialTrack;
+import cn.com.qmth.stmms.biz.mark.model.TrialTrackPK;
+
+public interface TrialTrackDao extends JpaRepository<TrialTrack, TrialTrackPK>, JpaSpecificationExecutor<TrialTrack> {
+
+    List<TrialTrack> findByPkLibraryIdAndPkMarkerId(Integer libraryId, Integer markerId);
+
+    List<TrialTrack> findByStudentId(Integer studentId);
+
+    @Modifying
+    @Query("delete from TrialTrack t where t.pk.libraryId=?1 and t.pk.markerId=?2")
+    void deleteByLibraryIdAndMarkerId(Integer libraryId, Integer markerId);
+
+    @Modifying
+    @Query("delete from TrialTrack t where t.studentId=?1")
+    void deleteByStudentId(Integer studentId);
+
+    @Modifying
+    @Query("delete from TrialTrack t where t.pk.markerId=?1")
+    void deleteByMarkerId(Integer markerId);
+
+    @Modifying
+    @Query("delete from TrialTrack t where t.examId=?1 and t.subjectCode=?2 and t.groupNumber=?3")
+    void deleteByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber);
+
+}

+ 168 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/MarkResult.java

@@ -0,0 +1,168 @@
+package cn.com.qmth.stmms.biz.mark.model;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 标准评卷结果对象
+ * 
+ * @author luoshi
+ *
+ */
+public class MarkResult {
+
+    /**
+     * 考生编号
+     */
+    private String studentId;
+
+    /**
+     * 评卷任务编号
+     */
+    private Integer libraryId;
+
+    /**
+     * 总分
+     */
+    private double totalScore;
+
+    /**
+     * 分值列表
+     */
+    private String scoreList;
+
+    /**
+     * 阅卷轨迹列表
+     */
+    private TrackDTO[] trackList;
+
+    /**
+     * 特殊标记列表
+     */
+    private SpecialTagDTO[] tagList;
+
+    /**
+     * 所花时间
+     */
+    private long spent;
+
+    public Integer getLibraryId() {
+        return libraryId;
+    }
+
+    public void setLibraryId(Integer libraryId) {
+        this.libraryId = libraryId;
+    }
+
+    public double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public String getScoreList() {
+        return scoreList;
+    }
+
+    public void setScoreList(String scoreList) {
+        this.scoreList = scoreList;
+    }
+
+    public TrackDTO[] getTrackList() {
+        return trackList;
+    }
+
+    public void setTrackList(TrackDTO[] trackList) {
+        this.trackList = trackList;
+    }
+
+    public SpecialTagDTO[] getTagList() {
+        return tagList;
+    }
+
+    public void setTagList(SpecialTagDTO[] tagList) {
+        this.tagList = tagList;
+    }
+
+    public long getSpent() {
+        return spent;
+    }
+
+    public void setSpent(long spent) {
+        this.spent = spent;
+    }
+
+    public String getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(String studentId) {
+        this.studentId = studentId;
+    }
+
+    public Map<String, List<TrackDTO>> getTrackMap() {
+        Map<String, List<TrackDTO>> map = new HashMap<String, List<TrackDTO>>();
+        if (trackList != null) {
+            for (TrackDTO dto : trackList) {
+                String questionNumber = dto.getQuestionNumber();
+                List<TrackDTO> list = map.get(questionNumber);
+                if (list == null) {
+                    list = new LinkedList<TrackDTO>();
+                    map.put(questionNumber, list);
+                }
+                list.add(dto);
+            }
+        }
+        return map;
+    }
+
+    public Map<String, List<MarkTrack>> getTrackMap(MarkLibrary library) {
+        Map<String, List<MarkTrack>> map = new HashMap<>();
+        if (trackList != null) {
+            for (TrackDTO dto : trackList) {
+                String questionNumber = dto.getQuestionNumber();
+                List<MarkTrack> list = map.get(questionNumber);
+                if (list == null) {
+                    list = new LinkedList<MarkTrack>();
+                    map.put(questionNumber, list);
+                }
+                list.add(dto.transform(library));
+            }
+        }
+        return map;
+    }
+
+    public List<MarkTrack> getTrackList(MarkLibrary library) {
+        List<MarkTrack> list = new LinkedList<>();
+        if (trackList != null) {
+            for (TrackDTO dto : trackList) {
+                list.add(dto.transform(library));
+            }
+        }
+        return list;
+    }
+
+    public List<TrialTrack> getTrackList(TrialHistory history) {
+        List<TrialTrack> list = new LinkedList<>();
+        if (trackList != null) {
+            for (TrackDTO dto : trackList) {
+                list.add(dto.transform(history));
+            }
+        }
+        return list;
+    }
+
+    public List<MarkSpecialTag> getSpecialTagList(MarkLibrary library) {
+        List<MarkSpecialTag> list = new LinkedList<>();
+        if (tagList != null) {
+            for (SpecialTagDTO dto : tagList) {
+                list.add(dto.transform(library));
+            }
+        }
+        return list;
+    }
+}

+ 1 - 161
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/Task.java

@@ -2,30 +2,12 @@ package cn.com.qmth.stmms.biz.mark.model;
 
 import java.io.Serializable;
 import java.util.Date;
-import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
 
-public class Task implements Serializable {
+public class Task extends MarkResult implements Serializable {
 
     private static final long serialVersionUID = 4912665442008033200L;
 
-    /**
-     * 考生编号
-     */
-    private String studentId;
-
-    /**
-     * 库ID
-     */
-    private int libraryId;
-
-    /**
-     * 评卷员Id
-     */
-    private int markId;
-
     /**
      * 题卡图片地址
      */
@@ -81,26 +63,6 @@ public class Task implements Serializable {
      */
     private boolean isProblem;
 
-    /**
-     * 总分
-     */
-    private double totalScore;
-
-    /**
-     * 分值列表
-     */
-    private String scoreList;
-
-    /**
-     * 阅卷轨迹列表
-     */
-    private TrackDTO[] trackList;
-
-    /**
-     * 所花时间
-     */
-    private long spent;
-
     /**
      * 问题类型
      */
@@ -131,39 +93,9 @@ public class Task implements Serializable {
 
     private String headerScoreList;
 
-    // 特殊标识:√ 或 ❌ 或 乄
-    private SpecialTagDTO[] tagList;
-
     // 仲裁记录集合
     private List<ArbitrationDTO> arbitrationList;
 
-    public Task() {
-    }
-
-    public String getStudentId() {
-        return studentId;
-    }
-
-    public void setStudentId(String studentId) {
-        this.studentId = studentId;
-    }
-
-    public int getLibraryId() {
-        return libraryId;
-    }
-
-    public void setLibraryId(int libraryId) {
-        this.libraryId = libraryId;
-    }
-
-    public int getMarkId() {
-        return markId;
-    }
-
-    public void setMarkId(int markId) {
-        this.markId = markId;
-    }
-
     public List<String> getPictureUrls() {
         return pictureUrls;
     }
@@ -196,30 +128,6 @@ public class Task implements Serializable {
         this.markStepList = markStepList;
     }
 
-    public double getTotalScore() {
-        return totalScore;
-    }
-
-    public void setTotalScore(double totalScore) {
-        this.totalScore = totalScore;
-    }
-
-    public String getScoreList() {
-        return scoreList;
-    }
-
-    public void setScoreList(String scoreList) {
-        this.scoreList = scoreList;
-    }
-
-    public long getSpent() {
-        return spent;
-    }
-
-    public void setSpent(long spent) {
-        this.spent = spent;
-    }
-
     public int getReason() {
         return reason;
     }
@@ -308,74 +216,6 @@ public class Task implements Serializable {
         this.isExist = isExist;
     }
 
-    public TrackDTO[] getTrackList() {
-        return trackList;
-    }
-
-    public void setTrackList(TrackDTO[] trackList) {
-        this.trackList = trackList;
-    }
-
-    public Map<String, List<TrackDTO>> getTrackMap() {
-        Map<String, List<TrackDTO>> map = new HashMap<String, List<TrackDTO>>();
-        if (trackList != null) {
-            for (TrackDTO dto : trackList) {
-                String questionNumber = dto.getQuestionNumber();
-                List<TrackDTO> list = map.get(questionNumber);
-                if (list == null) {
-                    list = new LinkedList<TrackDTO>();
-                    map.put(questionNumber, list);
-                }
-                list.add(dto);
-            }
-        }
-        return map;
-    }
-
-    public Map<String, List<MarkTrack>> getTrackMap(MarkLibrary library) {
-        Map<String, List<MarkTrack>> map = new HashMap<>();
-        if (trackList != null) {
-            for (TrackDTO dto : trackList) {
-                String questionNumber = dto.getQuestionNumber();
-                List<MarkTrack> list = map.get(questionNumber);
-                if (list == null) {
-                    list = new LinkedList<MarkTrack>();
-                    map.put(questionNumber, list);
-                }
-                list.add(dto.transform(library));
-            }
-        }
-        return map;
-    }
-
-    public List<MarkTrack> getTrackList(MarkLibrary library) {
-        List<MarkTrack> list = new LinkedList<>();
-        if (trackList != null) {
-            for (TrackDTO dto : trackList) {
-                list.add(dto.transform(library));
-            }
-        }
-        return list;
-    }
-
-    public SpecialTagDTO[] getTagList() {
-        return tagList;
-    }
-
-    public void setTagList(SpecialTagDTO[] tagList) {
-        this.tagList = tagList;
-    }
-
-    public List<MarkSpecialTag> getSpecialTagList(MarkLibrary library) {
-        List<MarkSpecialTag> list = new LinkedList<>();
-        if (tagList != null) {
-            for (SpecialTagDTO dto : tagList) {
-                list.add(dto.transform(library));
-            }
-        }
-        return list;
-    }
-
     public Integer getHeaderId() {
         return headerId;
     }

+ 17 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrackDTO.java

@@ -50,6 +50,23 @@ public class TrackDTO implements Serializable {
         return track;
     }
 
+    public TrialTrack transform(TrialHistory history) {
+        TrialTrack track = new TrialTrack();
+        track.setLibraryId(history.getLibraryId());
+        track.setMarkerId(history.getMarkerId());
+        track.setQuestionNumber(getQuestionNumber());
+        track.setNumber(getNumber());
+        track.setStudentId(history.getStudentId());
+        track.setExamId(history.getExamId());
+        track.setSubjectCode(history.getSubjectCode());
+        track.setGroupNumber(history.getGroupNumber());
+        track.setMarkerId(history.getMarkerId());
+        track.setScore(getScore());
+        track.setPositionX(getPositionX());
+        track.setPositionY(getPositionY());
+        return track;
+    }
+
     public String getQuestionNumber() {
         return questionNumber;
     }

+ 158 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialHistory.java

@@ -0,0 +1,158 @@
+package cn.com.qmth.stmms.biz.mark.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+
+import cn.com.qmth.stmms.biz.exam.model.Marker;
+
+/**
+ * 试评任务给分记录表
+ * 
+ * @author luoshi
+ *
+ */
+@Entity
+@Table(name = "m_trial_history")
+public class TrialHistory implements Serializable {
+
+    private static final long serialVersionUID = 1077169224382148814L;
+
+    @EmbeddedId
+    private TrialHistoryPK pk;
+
+    /**
+     * 考试ID
+     */
+    @Column(name = "exam_id")
+    private Integer examId;
+
+    /**
+     * 科目CODE
+     */
+    @Column(name = "subject_code")
+    private String subjectCode;
+
+    /**
+     * 大题序号
+     */
+    @Column(name = "group_number")
+    private Integer groupNumber;
+
+    /**
+     * 考生编号
+     */
+    @Column(name = "student_id")
+    private Integer studentId;
+
+    /**
+     * 评卷时间
+     */
+    @Column(name = "marker_time")
+    private Date markerTime;
+
+    /**
+     * 评卷员给分总分
+     */
+    @Column(name = "marker_score")
+    private Double markerScore;
+
+    /**
+     * 评卷员给分明细
+     */
+    @Column(name = "marker_score_list")
+    private String markerScoreList;
+
+    @Transient
+    private Marker marker;
+
+    public TrialHistory() {
+        this.pk = new TrialHistoryPK();
+    }
+
+    public Integer getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Integer examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public Integer getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Integer studentId) {
+        this.studentId = studentId;
+    }
+
+    public Integer getLibraryId() {
+        return pk.getLibraryId();
+    }
+
+    public void setLibraryId(Integer libraryId) {
+        pk.setLibraryId(libraryId);
+    }
+
+    public Integer getMarkerId() {
+        return pk.getMarkerId();
+    }
+
+    public void setMarkerId(Integer markerId) {
+        pk.setMarkerId(markerId);
+    }
+
+    public Date getMarkerTime() {
+        return markerTime;
+    }
+
+    public void setMarkerTime(Date markerTime) {
+        this.markerTime = markerTime;
+    }
+
+    public Double getMarkerScore() {
+        return markerScore;
+    }
+
+    public void setMarkerScore(Double markerScore) {
+        this.markerScore = markerScore;
+    }
+
+    public String getMarkerScoreList() {
+        return markerScoreList;
+    }
+
+    public void setMarkerScoreList(String markerScoreList) {
+        this.markerScoreList = markerScoreList;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public Marker getMarker() {
+        return marker;
+    }
+
+    public void setMarker(Marker marker) {
+        this.marker = marker;
+    }
+
+}

+ 66 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialHistoryPK.java

@@ -0,0 +1,66 @@
+package cn.com.qmth.stmms.biz.mark.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class TrialHistoryPK implements Serializable {
+
+    private static final long serialVersionUID = 7082004881704656373L;
+
+    @Column(name = "library_id", nullable = false)
+    private Integer libraryId;
+
+    @Column(name = "number", nullable = false)
+    private Integer markerId;
+
+    public Integer getLibraryId() {
+        return libraryId;
+    }
+
+    public void setLibraryId(Integer libraryId) {
+        this.libraryId = libraryId;
+    }
+
+    public Integer getMarkerId() {
+        return markerId;
+    }
+
+    public void setMarkerId(Integer markerId) {
+        this.markerId = markerId;
+    }
+
+    @Override
+    public int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + ((libraryId == null) ? 0 : libraryId.hashCode());
+        result = PRIME * result + ((markerId == null) ? 0 : markerId.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final TrialHistoryPK other = (TrialHistoryPK) obj;
+        if (libraryId == null) {
+            if (other.libraryId != null)
+                return false;
+        } else if (!libraryId.equals(other.libraryId))
+            if (markerId == null) {
+                if (other.markerId != null)
+                    return false;
+            } else if (!markerId.equals(other.markerId))
+                return false;
+
+        return true;
+    }
+
+}

+ 133 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialLibrary.java

@@ -0,0 +1,133 @@
+package cn.com.qmth.stmms.biz.mark.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * 试评任务表
+ * 
+ * @author luoshi
+ *
+ */
+@Entity
+@Table(name = "m_trial_library")
+public class TrialLibrary implements Serializable {
+
+    private static final long serialVersionUID = 6791719880545458247L;
+
+    @Id
+    @GeneratedValue
+    private Integer id;
+
+    /**
+     * 考试ID
+     */
+    @Column(name = "exam_id")
+    private Integer examId;
+
+    /**
+     * 科目代码
+     */
+    @Column(name = "subject_code")
+    private String subjectCode;
+
+    /**
+     * 大题序号
+     */
+    @Column(name = "group_number")
+    private Integer groupNumber;
+
+    /**
+     * 学习中心
+     */
+    @Column(name = "campus_id")
+    private Integer campusId;
+
+    /**
+     * 考生编号
+     */
+    @Column(name = "student_id")
+    private Integer studentId;
+
+    /**
+     * 准考证号
+     */
+    @Column(name = "exam_number")
+    private String examNumber;
+
+    /**
+     * 已评人数
+     */
+    @Column(name = "mark_count")
+    private Integer markCount;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Integer examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public Integer getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Integer studentId) {
+        this.studentId = studentId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public Integer getCampusId() {
+        return campusId;
+    }
+
+    public void setCampusId(Integer campusId) {
+        this.campusId = campusId;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public Integer getMarkCount() {
+        return markCount;
+    }
+
+    public void setMarkCount(Integer markCount) {
+        this.markCount = markCount;
+    }
+
+}

+ 143 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialTrack.java

@@ -0,0 +1,143 @@
+package cn.com.qmth.stmms.biz.mark.model;
+
+import javax.persistence.Column;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+/**
+ * 试评阅卷轨迹
+ * 
+ * @author luoshi
+ * 
+ */
+@Entity
+@Table(name = "m_trial_track")
+public class TrialTrack {
+
+    @EmbeddedId
+    private TrialTrackPK pk;
+
+    @Column(name = "student_id", nullable = false)
+    private Integer studentId;
+
+    @Column(name = "exam_id", nullable = false)
+    private Integer examId;
+
+    @Column(name = "subject_code", nullable = false)
+    private String subjectCode;
+
+    @Column(name = "group_number", nullable = false)
+    private Integer groupNumber;
+
+    /**
+     * 轨迹分数
+     */
+    @Column(name = "score", nullable = false)
+    private Double score;
+
+    /**
+     * X轴相对位置,0~1
+     */
+    @Column(name = "position_x", nullable = false)
+    private Double positionX;
+
+    /**
+     * Y轴相对位置,0~1
+     */
+    @Column(name = "position_y", nullable = false)
+    private Double positionY;
+
+    public TrialTrack() {
+        this.pk = new TrialTrackPK();
+    }
+
+    public Integer getLibraryId() {
+        return pk.getLibraryId();
+    }
+
+    public void setLibraryId(Integer libraryId) {
+        pk.setLibraryId(libraryId);
+    }
+
+    public Integer getMarkerId() {
+        return pk.getMarkerId();
+    }
+
+    public void setMarkerId(Integer markerId) {
+        pk.setMarkerId(markerId);
+    }
+
+    public String getQuestionNumber() {
+        return pk.getQuestionNumber();
+    }
+
+    public void setQuestionNumber(String questionNumber) {
+        pk.setQuestionNumber(questionNumber);
+    }
+
+    public Integer getNumber() {
+        return pk.getNumber();
+    }
+
+    public void setNumber(Integer number) {
+        pk.setNumber(number);
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    public Double getPositionX() {
+        return positionX;
+    }
+
+    public void setPositionX(Double positionX) {
+        this.positionX = positionX;
+    }
+
+    public Double getPositionY() {
+        return positionY;
+    }
+
+    public void setPositionY(Double positionY) {
+        this.positionY = positionY;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public Integer getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Integer studentId) {
+        this.studentId = studentId;
+    }
+
+    public Integer getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Integer examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+}

+ 101 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/TrialTrackPK.java

@@ -0,0 +1,101 @@
+package cn.com.qmth.stmms.biz.mark.model;
+
+import java.io.Serializable;
+
+import javax.persistence.Column;
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class TrialTrackPK implements Serializable {
+
+    private static final long serialVersionUID = 9096523417236451724L;
+
+    @Column(name = "library_id", nullable = false)
+    private Integer libraryId;
+
+    @Column(name = "marker_id", nullable = false)
+    private Integer markerId;
+
+    @Column(name = "question_number", nullable = false)
+    private String questionNumber;
+
+    @Column(name = "number", nullable = false)
+    private Integer number;
+
+    public Integer getLibraryId() {
+        return libraryId;
+    }
+
+    public void setLibraryId(Integer libraryId) {
+        this.libraryId = libraryId;
+    }
+
+    public Integer getMarkerId() {
+        return markerId;
+    }
+
+    public void setMarkerId(Integer markerId) {
+        this.markerId = markerId;
+    }
+
+    public String getQuestionNumber() {
+        return questionNumber;
+    }
+
+    public void setQuestionNumber(String questionNumber) {
+        this.questionNumber = questionNumber;
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    @Override
+    public int hashCode() {
+        final int PRIME = 31;
+        int result = 1;
+        result = PRIME * result + ((libraryId == null) ? 0 : libraryId.hashCode());
+        result = PRIME * result + ((markerId == null) ? 0 : markerId.hashCode());
+        result = PRIME * result + ((questionNumber == null) ? 0 : questionNumber.hashCode());
+        result = PRIME * result + ((number == null) ? 0 : number.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final TrialTrackPK other = (TrialTrackPK) obj;
+        if (libraryId == null) {
+            if (other.libraryId != null)
+                return false;
+        } else if (!libraryId.equals(other.libraryId))
+            return false;
+        if (markerId == null) {
+            if (other.markerId != null)
+                return false;
+        } else if (!markerId.equals(other.markerId))
+            return false;
+        if (questionNumber == null) {
+            if (other.questionNumber != null)
+                return false;
+        } else if (!questionNumber.equals(other.questionNumber))
+            return false;
+        if (number == null) {
+            if (other.number != null)
+                return false;
+        } else if (!number.equals(other.number))
+            return false;
+
+        return true;
+    }
+
+}

+ 69 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/query/TrialLibrarySearchQuery.java

@@ -0,0 +1,69 @@
+package cn.com.qmth.stmms.biz.mark.query;
+
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+
+import cn.com.qmth.stmms.biz.common.BaseQuery;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
+
+public class TrialLibrarySearchQuery extends BaseQuery<TrialLibrary> {
+
+    private Integer examId;
+
+    private String subjectCode;
+
+    private Integer groupNumber;
+
+    private Integer studentId;
+
+    private String examNumber;
+
+    public void orderByExamNumber() {
+        setSort(new Sort(Direction.ASC, "examNumber"));
+    }
+
+    public void orderById() {
+        setSort(new Sort(Direction.ASC, "id"));
+    }
+
+    public Integer getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Integer examId) {
+        this.examId = examId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public Integer getGroupNumber() {
+        return groupNumber;
+    }
+
+    public void setGroupNumber(Integer groupNumber) {
+        this.groupNumber = groupNumber;
+    }
+
+    public Integer getStudentId() {
+        return studentId;
+    }
+
+    public void setStudentId(Integer studentId) {
+        this.studentId = studentId;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+}

+ 76 - 52
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkCronService.java

@@ -9,26 +9,22 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.data.domain.Sort;
-import org.springframework.data.domain.Sort.Direction;
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Component;
 
 import cn.com.qmth.stmms.biz.campus.model.Campus;
 import cn.com.qmth.stmms.biz.campus.service.CampusService;
-import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
-import cn.com.qmth.stmms.biz.exam.query.ExamSearchQuery;
-import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.lock.impl.MemoryLockProvider;
 import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.biz.utils.CurrentTaskUtil;
-import cn.com.qmth.stmms.common.enums.ExamStatus;
+import cn.com.qmth.stmms.biz.utils.TrialTaskUtil;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 
 /**
  * 与评卷相关的所有定时任务
@@ -41,9 +37,6 @@ public class MarkCronService {
 
     protected static final Logger log = LoggerFactory.getLogger(MarkCronService.class);
 
-    @Autowired
-    private ExamService examService;
-
     @Autowired
     private CampusService campusService;
 
@@ -88,6 +81,7 @@ public class MarkCronService {
     public void cronCleanTask() {
         try {
             CurrentTaskUtil.clearTimeoutTask(timeoutMinute);
+            TrialTaskUtil.clearTimeoutTask(timeoutMinute);
         } catch (Exception e) {
             log.error("CronCleanTask error", e);
         }
@@ -101,19 +95,9 @@ public class MarkCronService {
         log.debug("start auto-create library");
         try {
             Map<String, Campus> campusMap = new HashMap<String, Campus>();
-
-            ExamSearchQuery query = new ExamSearchQuery();
-            query.addStatus(ExamStatus.START);
-            query.setSort(new Sort(Direction.DESC, "id"));
-            query.setPageNumber(1);
-            query.setPageSize(10);
-            query = examService.findByQuery(query);
-            while (query.getCurrentCount() > 0) {
-                for (Exam exam : query.getResult()) {
-                    buildLibraryByExam(exam, campusMap);
-                }
-                query.setPageNumber(query.getPageNumber() + 1);
-                query = examService.findByQuery(query);
+            List<Integer> examIds = groupService.findExamIdByStatus(MarkStatus.TRIAL, MarkStatus.FORMAL);
+            for (Integer examId : examIds) {
+                buildLibraryByExam(examId, campusMap);
             }
         } catch (Exception e) {
             log.error("auto-create library error", e);
@@ -122,9 +106,9 @@ public class MarkCronService {
         }
     }
 
-    private void buildLibraryByExam(Exam exam, Map<String, Campus> campusMap) {
+    private void buildLibraryByExam(Integer examId, Map<String, Campus> campusMap) {
         // 获取主观题总分大于0的科目
-        List<ExamSubject> subjects = subjectService.list(exam.getId(), 0);
+        List<ExamSubject> subjects = subjectService.list(examId, 0);
         for (ExamSubject subject : subjects) {
             // 清除缺考考生和违纪考生
             List<ExamStudent> list = studentService.findAbsentOrBreachLibraryStudent(subject.getExamId(),
@@ -135,36 +119,76 @@ public class MarkCronService {
                 }
             }
             // 处理正常考生
-            List<MarkGroup> groups = groupService.findByExamAndSubject(subject.getExamId(), subject.getCode());
-            for (MarkGroup group : groups) {
-                Date lastBuildTime = group.getBuildTime();
-                ExamStudent student = studentService.findUnLibraryStudent(exam.getId(), subject.getCode(),
-                        group.getNumber(), lastBuildTime);
-                while (student != null) {
-                    // 补充学习中心集合
-                    Campus campus = campusMap.get(student.getSchoolId() + "_" + student.getCampusName());
-                    if (campus == null) {
-                        campus = campusService.findBySchoolAndName(student.getSchoolId(), student.getCampusName());
-                        if (campus == null) {
-                            log.error("campus unexist for student id=" + student.getId());
-                            continue;
-                        } else {
-                            campusMap.put(student.getSchoolId() + "_" + student.getCampusName(), campus);
-                        }
-                    }
-                    // 尝试构造评卷任务
-                    try {
-                        markService.buildLibrary(student, campus, group);
-                        lastBuildTime = student.getUploadTime();
-                    } catch (Exception e) {
-                        log.error("build library error for studentId=" + student.getId() + ", groupNumber="
-                                + group.getNumber(), e);
-                    }
-                    // 取下一个考生
-                    student = studentService.findUnLibraryStudent(exam.getId(), subject.getCode(), group.getNumber(),
-                            lastBuildTime);
+            // 生成正评任务
+            buildFormalLibrary(examId, subject.getCode(), campusMap);
+            // 生成试评任务
+            buildTrialLibrary(examId, subject.getCode(), campusMap);
+        }
+    }
+
+    private void buildFormalLibrary(Integer examId, String subjectCode, Map<String, Campus> campusMap) {
+        List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(examId, subjectCode, MarkStatus.FORMAL);
+        for (MarkGroup group : groups) {
+            Date lastBuildTime = group.getBuildTime();
+            ExamStudent student = studentService.findUnLibraryStudent(examId, subjectCode, group.getNumber(),
+                    lastBuildTime);
+            while (student != null) {
+                // 补充学习中心集合
+                Campus campus = getCampus(student, campusMap);
+                if (campus == null) {
+                    log.error("campus unexist for student id=" + student.getId());
+                    continue;
+                }
+                // 尝试构造评卷任务
+                try {
+                    markService.buildFormalLibrary(student, campus, group);
+                    lastBuildTime = student.getUploadTime();
+                } catch (Exception e) {
+                    log.error("build formal library error for studentId=" + student.getId() + ", groupNumber="
+                            + group.getNumber(), e);
+                }
+                // 取下一个考生
+                student = studentService.findUnLibraryStudent(examId, subjectCode, group.getNumber(), lastBuildTime);
+            }
+        }
+    }
+
+    private void buildTrialLibrary(Integer examId, String subjectCode, Map<String, Campus> campusMap) {
+        List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(examId, subjectCode, MarkStatus.TRIAL);
+        for (MarkGroup group : groups) {
+            if (group.getLibraryCount() >= group.getTrialCount()) {
+                continue;
+            }
+
+            while (groupService.needTrialLibrary(examId, subjectCode, group.getNumber())) {
+                // 随机取一个未生成试评任务的考生
+                ExamStudent student = studentService.randomUnTrialStudent(examId, subjectCode, group.getNumber());
+                // 补充学习中心集合
+                Campus campus = getCampus(student, campusMap);
+                if (campus == null) {
+                    log.error("campus unexist for student id=" + student.getId());
+                    continue;
                 }
+                // 尝试构造试评任务
+                try {
+                    markService.buildTrialLibrary(student, campus, group);
+                } catch (Exception e) {
+                    log.error("build trial library error for studentId=" + student.getId() + ", groupNumber="
+                            + group.getNumber(), e);
+                }
+            }
+        }
+    }
+
+    private Campus getCampus(ExamStudent student, Map<String, Campus> campusMap) {
+        // 补充学习中心集合
+        Campus campus = campusMap.get(student.getSchoolId() + "_" + student.getCampusName());
+        if (campus == null) {
+            campus = campusService.findBySchoolAndName(student.getSchoolId(), student.getCampusName());
+            if (campus != null) {
+                campusMap.put(student.getSchoolId() + "_" + student.getCampusName(), campus);
             }
         }
+        return campus;
     }
 }

+ 1 - 1
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkLockService.java

@@ -79,7 +79,7 @@ public class MarkLockService {
      * 
      * @param studentId
      */
-    public void waitUnlockStudent(Integer studentId) {
+    public void waitUnlockStudent(Object studentId) {
         waitUnlock(getLock(studentMap, studentId));
     }
 

+ 280 - 119
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkServiceImpl.java

@@ -25,16 +25,21 @@ import cn.com.qmth.stmms.biz.mark.dao.ArbitrateHistoryDao;
 import cn.com.qmth.stmms.biz.mark.dao.MarkLibraryDao;
 import cn.com.qmth.stmms.biz.mark.dao.MarkSpecialTagDao;
 import cn.com.qmth.stmms.biz.mark.dao.MarkTrackDao;
+import cn.com.qmth.stmms.biz.mark.dao.TrialHistoryDao;
+import cn.com.qmth.stmms.biz.mark.dao.TrialLibraryDao;
+import cn.com.qmth.stmms.biz.mark.dao.TrialTrackDao;
 import cn.com.qmth.stmms.biz.mark.model.ArbitrateHistory;
 import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
-import cn.com.qmth.stmms.biz.mark.model.MarkSpecialTag;
-import cn.com.qmth.stmms.biz.mark.model.MarkTrack;
+import cn.com.qmth.stmms.biz.mark.model.MarkResult;
+import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
 import cn.com.qmth.stmms.biz.mark.service.MarkService;
-import cn.com.qmth.stmms.biz.user.model.User;
 import cn.com.qmth.stmms.biz.utils.CurrentTaskUtil;
 import cn.com.qmth.stmms.biz.utils.ScoreItem;
+import cn.com.qmth.stmms.biz.utils.TrialTaskUtil;
 import cn.com.qmth.stmms.common.enums.HistoryStatus;
 import cn.com.qmth.stmms.common.enums.LibraryStatus;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.ScorePolicy;
 
 /**
@@ -75,6 +80,15 @@ public class MarkServiceImpl implements MarkService {
     @Autowired
     private MarkSpecialTagDao specialTagDao;
 
+    @Autowired
+    private TrialLibraryDao trialLibraryDao;
+
+    @Autowired
+    private TrialHistoryDao trialHistoryDao;
+
+    @Autowired
+    private TrialTrackDao trialTrackDao;
+
     @Autowired
     private MarkLockService lockService;
 
@@ -86,7 +100,13 @@ public class MarkServiceImpl implements MarkService {
      */
     @Override
     public int applyCount(MarkGroup group) {
-        return CurrentTaskUtil.count(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        int count = 0;
+        if (group.getStatus() == MarkStatus.TRIAL) {
+            count = TrialTaskUtil.count(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        } else if (group.getStatus() == MarkStatus.FORMAL) {
+            count = CurrentTaskUtil.count(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        }
+        return count;
     }
 
     /**
@@ -107,7 +127,11 @@ public class MarkServiceImpl implements MarkService {
      */
     @Override
     public void releaseByGroup(MarkGroup group) {
-        CurrentTaskUtil.clear(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        if (group.getStatus() == MarkStatus.TRIAL) {
+            TrialTaskUtil.clear(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        } else if (group.getStatus() == MarkStatus.FORMAL) {
+            CurrentTaskUtil.clear(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        }
     }
 
     /**
@@ -138,8 +162,13 @@ public class MarkServiceImpl implements MarkService {
     public void deleteGroup(MarkGroup group) {
         try {
             lockService.lockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+
             questionDao.deleteByExamIdAndSubjectCodeAndPaperTypeAndObjectiveAndMainNumber(group.getExamId(),
                     group.getSubjectCode(), null, false, group.getNumber());
+            trialLibraryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+            trialHistoryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
             libraryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
                     group.getNumber());
             arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
@@ -201,7 +230,7 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 评卷员申请领取某个评卷任务
+     * 评卷员申请领取某个正式评卷任务
      * 
      * @param library
      * @param marker
@@ -218,7 +247,18 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 评卷员是否已领取了某个评卷任务
+     * 评卷员申请领取某个试评评卷任务
+     * 
+     * @param library
+     * @param marker
+     */
+    @Override
+    public boolean applyLibrary(TrialLibrary library, Marker marker) {
+        return TrialTaskUtil.add(marker, getApplyTaskId(library, marker));
+    }
+
+    /**
+     * 评卷员是否已领取了某个正式评卷任务
      * 
      * @param library
      * @param marker
@@ -230,7 +270,19 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 释放某个评卷员已领取的任务
+     * 评卷员是否已领取了某个试评评卷任务
+     * 
+     * @param library
+     * @param marker
+     * @return
+     */
+    @Override
+    public boolean hasApplied(TrialLibrary library, Marker marker) {
+        return TrialTaskUtil.exists(marker, getApplyTaskId(library, marker));
+    }
+
+    /**
+     * 释放某个评卷员已领取的正评任务
      * 
      * @param library
      * @param marker
@@ -240,6 +292,17 @@ public class MarkServiceImpl implements MarkService {
         CurrentTaskUtil.remove(marker, getApplyTaskId(library));
     }
 
+    /**
+     * 释放某个评卷员已领取的试评任务
+     * 
+     * @param library
+     * @param marker
+     */
+    @Override
+    public void releaseLibrary(TrialLibrary library, Marker marker) {
+        TrialTaskUtil.remove(marker, getApplyTaskId(library, marker));
+    }
+
     /**
      * 释放某个评卷员的所有锁定任务
      * 
@@ -248,6 +311,7 @@ public class MarkServiceImpl implements MarkService {
     @Override
     public void releaseByMarker(Marker marker) {
         CurrentTaskUtil.clear(marker);
+        TrialTaskUtil.clear(marker);
     }
 
     /**
@@ -289,6 +353,8 @@ public class MarkServiceImpl implements MarkService {
             specialTagDao.deleteByStudentId(student.getId());
             libraryDao.deleteByStudentId(student.getId());
             arbitrateDao.deleteByStudentId(student.getId());
+            trialLibraryDao.deleteByStudentId(student.getId());
+            trialHistoryDao.deleteByStudentId(student.getId());
             updateLibraryCount(student.getExamId(), student.getSubjectCode());
         } catch (Exception e) {
             throw e;
@@ -298,105 +364,145 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 评卷员提交某个评卷任务
+     * 评卷员提交评卷任务
      * 
-     * @param library
-     * @param trackMap
-     * @param tagList
+     * @param task
+     * @param marker
+     * @return
      */
     @Override
     @Transactional
-    public boolean submitLibrary(MarkLibrary library, Marker marker, List<MarkTrack> trackList,
-            List<MarkSpecialTag> tagList) {
-        // 判断评卷任务记录是否存在,以及评卷员是否有权限操作
-        if (library == null || !library.getExamId().equals(marker.getExamId())
-                || !library.getSubjectCode().equals(marker.getSubjectCode())
-                || !library.getGroupNumber().equals(marker.getGroupNumber())) {
-            return false;
-        }
-        // 判断大题是否存在
-        MarkGroup group = groupDao.findOne(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
-        if (group == null) {
+    public boolean submitTask(MarkResult result, Marker marker) {
+        // 判断大题是否存在/评卷是否结束
+        MarkGroup group = groupDao.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+        if (group == null || group.getStatus() == MarkStatus.FINISH) {
             return false;
         }
         // 等待大题释放锁定
         lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
         // 等待考生释放锁定
-        lockService.waitUnlockStudent(library.getStudentId());
-
+        lockService.waitUnlockStudent(result.getStudentId());
         try {
-            // 非本人领取的待评任务
-            if (library.getStatus() == LibraryStatus.WAITING && !hasApplied(library, marker)) {
-                return false;
-            }
-            // 非本人的回评任务
-            if (library.getStatus() == LibraryStatus.MARKED && !library.getMarkerId().equals(marker.getId())) {
-                return false;
-            }
-            // 校验总分是否超标
-            if (library.getMarkerScore() > group.getTotalScore() || StringUtils.isBlank(library.getMarkerScoreList())) {
-                return false;
-            }
-            // 是否多评情况下已处理过该考生评卷任务
-            if (libraryDao.countByStudentIdAndMarkerIdAndIdNotEqual(library.getStudentId(), library.getMarkerId(),
-                    library.getId()) > 0) {
-                return false;
-            }
-            // 尝试提交评卷结果
-            Date now = new Date();
-            if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.MARKED, library.getMarkerId(),
-                    library.getMarkerScore(), library.getMarkerScoreList(), now, library.getTags(),
-                    LibraryStatus.WAITING, LibraryStatus.MARKED) == 0) {
-                // 条件不符更新失败,直接返回
-                return false;
-            }
-            // 保存阅卷轨迹
-            if (trackList != null) {
-                trackDao.deleteByLibraryId(library.getId());
-                trackDao.save(trackList);
-            }
-            // 保存特殊标记
-            if (tagList != null) {
-                specialTagDao.deleteByLibraryId(library.getId());
-                specialTagDao.save(tagList);
-            }
-            // 判断多评模式下是否需要仲裁
-            ArbitrateHistory history = null;
-            if (group.getArbitrateThreshold() != null && group.getArbitrateThreshold() > 0) {
-                // 多评模式
-                List<MarkLibrary> list = libraryDao.findByStudentIdAndGroupNumberAndStatus(library.getStudentId(),
-                        library.getGroupNumber(), LibraryStatus.MARKED);
-                for (MarkLibrary other : list) {
-                    if (other.getId().equals(library.getId()) || other.getHeaderScore() != null) {
-                        // 本评卷任务或组长已打分,则跳过该任务
-                        continue;
+            // 根据评卷状态选择读取不同的评卷任务
+            if (group.getStatus() == MarkStatus.FORMAL) {
+                MarkLibrary library = libraryDao.findOne(result.getLibraryId());
+                if (library != null && library.getExamId().equals(group.getExamId())
+                        && library.getSubjectCode().equals(group.getSubjectCode())
+                        && library.getGroupNumber().equals(group.getNumber())
+                        && result.getTotalScore() <= group.getTotalScore()
+                        && StringUtils.isNotBlank(result.getScoreList())) {
+                    if (submitLibrary(library, marker, group, result)) {
+                        updateLibraryCount(group);
+                        releaseLibrary(library, marker);
+                        return true;
                     }
-                    if (Math.abs(other.getMarkerScore() - library.getMarkerScore()) > group.getArbitrateThreshold()) {
-                        // 分差超过阀值,触发仲裁
-                        history = new ArbitrateHistory();
-                        history.setExamId(library.getExamId());
-                        history.setSubjectCode(library.getSubjectCode());
-                        history.setGroupNumber(library.getGroupNumber());
-                        history.setStudentId(library.getStudentId());
-                        history.setExamNumber(library.getExamNumber());
-                        history.setStatus(HistoryStatus.WAITING);
-                        history.setCreateTime(now);
-                        break;
+                }
+            } else if (group.getStatus() == MarkStatus.TRIAL) {
+                TrialLibrary library = trialLibraryDao.findOne(result.getLibraryId());
+                if (library != null && library.getExamId().equals(group.getExamId())
+                        && library.getSubjectCode().equals(group.getSubjectCode())
+                        && library.getGroupNumber().equals(group.getNumber())
+                        && result.getTotalScore() <= group.getTotalScore()
+                        && StringUtils.isNotBlank(result.getScoreList())) {
+                    TrialHistory history = new TrialHistory();
+                    history.setExamId(library.getExamId());
+                    history.setSubjectCode(library.getSubjectCode());
+                    history.setGroupNumber(library.getGroupNumber());
+                    history.setLibraryId(library.getId());
+                    history.setStudentId(library.getStudentId());
+                    history.setMarkerId(marker.getId());
+                    history.setMarkerTime(new Date());
+                    history.setMarkerScore(result.getTotalScore());
+                    history.setMarkerScoreList(result.getScoreList());
+                    history = trialHistoryDao.save(history);
+                    if (history != null) {
+                        if (result.getTrackList() != null) {
+                            trialTrackDao.deleteByLibraryIdAndMarkerId(history.getLibraryId(), history.getMarkerId());
+                            trialTrackDao.save(result.getTrackList(history));
+                        }
+                        releaseLibrary(library, marker);
+                        return true;
                     }
                 }
             }
-            if (history != null) {
-                // 保存仲裁记录
-                arbitrateDao.save(history);
-                // 触发仲裁后续状态更新
-                libraryDao.updateByStudentIdAndGroupNumber(library.getStudentId(), library.getGroupNumber(),
-                        LibraryStatus.WAIT_ARBITRATE);
-            }
-            updateLibraryCount(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
-            return true;
         } catch (Exception e) {
-            throw new RuntimeException("submit library error", e);
+            throw new RuntimeException("submit task error", e);
         }
+        return false;
+    }
+
+    /**
+     * 评卷员提交某个正评任务
+     * 
+     * @param library
+     * @param marker
+     * @param trackMap
+     * @param tagList
+     */
+    private boolean submitLibrary(MarkLibrary library, Marker marker, MarkGroup group, MarkResult result) {
+        // 非本人领取的待评任务
+        if (library.getStatus() == LibraryStatus.WAITING && !hasApplied(library, marker)) {
+            return false;
+        }
+        // 非本人的回评任务
+        if (library.getStatus() == LibraryStatus.MARKED && !library.getMarkerId().equals(marker.getId())) {
+            return false;
+        }
+        // 是否多评情况下已处理过该考生评卷任务
+        if (libraryDao.countByStudentIdAndMarkerIdAndIdNotEqual(library.getStudentId(), library.getMarkerId(),
+                library.getId()) > 0) {
+            return false;
+        }
+        // 尝试提交评卷结果
+        Date now = new Date();
+        if (libraryDao.updateMarkerResult(library.getId(), LibraryStatus.MARKED, marker.getId(), result.getTotalScore(),
+                result.getScoreList(), now, null, LibraryStatus.WAITING, LibraryStatus.MARKED) == 0) {
+            // 条件不符更新失败,直接返回
+            return false;
+        }
+        // 保存阅卷轨迹
+        if (result.getTrackList() != null) {
+            trackDao.deleteByLibraryId(library.getId());
+            trackDao.save(result.getTrackList(library));
+        }
+        // 保存特殊标记
+        if (result.getTagList() != null) {
+            specialTagDao.deleteByLibraryId(library.getId());
+            specialTagDao.save(result.getSpecialTagList(library));
+        }
+        // 判断多评模式下是否需要仲裁
+        ArbitrateHistory history = null;
+        if (group.getArbitrateThreshold() != null && group.getArbitrateThreshold() > 0) {
+            // 多评模式
+            List<MarkLibrary> list = libraryDao.findByStudentIdAndGroupNumberAndStatus(library.getStudentId(),
+                    library.getGroupNumber(), LibraryStatus.MARKED);
+            for (MarkLibrary other : list) {
+                if (other.getId().equals(library.getId()) || other.getHeaderScore() != null) {
+                    // 本评卷任务或组长已打分,则跳过该任务
+                    continue;
+                }
+                if (Math.abs(other.getMarkerScore() - library.getMarkerScore()) > group.getArbitrateThreshold()) {
+                    // 分差超过阀值,触发仲裁
+                    history = new ArbitrateHistory();
+                    history.setExamId(library.getExamId());
+                    history.setSubjectCode(library.getSubjectCode());
+                    history.setGroupNumber(library.getGroupNumber());
+                    history.setStudentId(library.getStudentId());
+                    history.setExamNumber(library.getExamNumber());
+                    history.setStatus(HistoryStatus.WAITING);
+                    history.setCreateTime(now);
+                    break;
+                }
+            }
+        }
+        if (history != null) {
+            // 保存仲裁记录
+            arbitrateDao.save(history);
+            // 触发仲裁后续状态更新
+            libraryDao.updateByStudentIdAndGroupNumber(library.getStudentId(), library.getGroupNumber(),
+                    LibraryStatus.WAIT_ARBITRATE);
+        }
+        return true;
     }
 
     /**
@@ -419,18 +525,6 @@ public class MarkServiceImpl implements MarkService {
         }
     }
 
-    /**
-     * 管理员/组长直接对考生打分
-     * 
-     * @param student
-     * @param user
-     */
-    @Override
-    @Transactional
-    public void submitStudent(ExamStudent student, User user) {
-        // TODO - 下个版本添加此功能
-    }
-
     /**
      * 管理员/组长处理仲裁卷
      * 
@@ -550,10 +644,33 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Transactional
     public void updateLibraryCount(Integer examId, String subjectCode, Integer groupNumber) {
-        groupDao.updateLibraryCount(examId, subjectCode, groupNumber,
-                (int) libraryDao.countByExamIdAndSubjectCodeAndGroupNumber(examId, subjectCode, groupNumber),
-                (int) libraryDao.countByExamIdAndSubjectCodeAndGroupNumberAndStatus(examId, subjectCode, groupNumber,
-                        LibraryStatus.MARKED, LibraryStatus.ARBITRATED));
+        MarkGroup group = groupDao.findOne(examId, subjectCode, groupNumber);
+        if (group != null) {
+            updateLibraryCount(group);
+        }
+
+    }
+
+    /**
+     * 更新某个大题评卷任务数量
+     * 
+     * @param group
+     */
+    @Override
+    @Transactional
+    public void updateLibraryCount(MarkGroup group) {
+        if (group.getStatus() == MarkStatus.FORMAL) {
+            groupDao.updateLibraryCount(group.getExamId(), group.getSubjectCode(), group.getNumber(),
+                    (int) libraryDao.countByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(),
+                            group.getSubjectCode(), group.getNumber()),
+                    (int) libraryDao.countByExamIdAndSubjectCodeAndGroupNumberAndStatus(group.getExamId(),
+                            group.getSubjectCode(), group.getNumber(), LibraryStatus.MARKED, LibraryStatus.ARBITRATED));
+        } else if (group.getStatus() == MarkStatus.TRIAL) {
+            groupDao.updateLibraryCount(group.getExamId(), group.getSubjectCode(), group.getNumber(),
+                    (int) trialLibraryDao.countByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(),
+                            group.getSubjectCode(), group.getNumber()),
+                    (int) trialLibraryDao.countMarked(group.getExamId(), group.getSubjectCode(), group.getNumber()));
+        }
     }
 
     /**
@@ -568,12 +685,12 @@ public class MarkServiceImpl implements MarkService {
     public void updateLibraryCount(Integer examId, String subjectCode) {
         List<MarkGroup> groups = groupDao.findByExamIdAndSubjectCode(examId, subjectCode);
         for (MarkGroup group : groups) {
-            updateLibraryCount(examId, subjectCode, group.getNumber());
+            updateLibraryCount(group);
         }
     }
 
     /**
-     * 根据考生、学习中心、大题构造评卷任务
+     * 根据考生、学习中心、大题构造正式评卷任务
      * 
      * @param student
      * @param campus
@@ -581,7 +698,7 @@ public class MarkServiceImpl implements MarkService {
      */
     @Override
     @Transactional
-    public void buildLibrary(ExamStudent student, Campus campus, MarkGroup group) {
+    public void buildFormalLibrary(ExamStudent student, Campus campus, MarkGroup group) {
         // 等待大题释放锁定
         lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
         // 等待考生释放锁定
@@ -614,11 +731,40 @@ public class MarkServiceImpl implements MarkService {
         }
         group.setBuildTime(student.getUploadTime());
         groupDao.updateBuildTime(group.getExamId(), group.getSubjectCode(), group.getNumber(), student.getUploadTime());
-        updateLibraryCount(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        updateLibraryCount(group);
+    }
+
+    /**
+     * 根据考生、学习中心、大题构造试评评卷任务
+     * 
+     * @param student
+     * @param campus
+     * @param group
+     */
+    @Override
+    @Transactional
+    public void buildTrialLibrary(ExamStudent student, Campus campus, MarkGroup group) {
+        // 等待大题释放锁定
+        lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        // 等待考生释放锁定
+        lockService.waitUnlockStudent(student.getId());
+        // 查询是否已创建评卷任务
+        if (trialLibraryDao.countByStudentIdAndGroupNumber(student.getId(), group.getNumber()) == 0) {
+            TrialLibrary library = new TrialLibrary();
+            library.setExamId(student.getExamId());
+            library.setSubjectCode(student.getSubjectCode());
+            library.setGroupNumber(group.getNumber());
+            library.setCampusId(campus.getId());
+            library.setStudentId(student.getId());
+            library.setExamNumber(student.getExamNumber());
+            library.setMarkCount(0);
+            trialLibraryDao.save(library);
+            updateLibraryCount(group);
+        }
     }
 
     /**
-     * 领取评卷任务时,用来区分的唯一标识<br/>
+     * 领取正式评卷任务时,用来区分的唯一标识<br/>
      * 多评时同一个考生的多份任务不能被同一位评卷员领取
      * 
      * @param library
@@ -628,6 +774,16 @@ public class MarkServiceImpl implements MarkService {
         return library.getStudentId() + "_" + library.getGroupNumber();
     }
 
+    /**
+     * 领取试评评卷任务时,用来区分的唯一标识
+     * 
+     * @param library
+     * @return
+     */
+    private String getApplyTaskId(TrialLibrary library, Marker marker) {
+        return library.getId() + "_" + marker.getId();
+    }
+
     /**
      * 重置评卷分组的连带操作
      * 
@@ -635,13 +791,18 @@ public class MarkServiceImpl implements MarkService {
      */
     private void resetGroup(MarkGroup group) {
         groupDao.resetCount(group.getExamId(), group.getSubjectCode(), group.getNumber());
-        libraryDao.resetByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
-                group.getNumber(), LibraryStatus.WAITING);
-        arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
-                group.getNumber());
-        trackDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
-                group.getNumber());
-        specialTagDao.deleteByExamAndSubjectAndGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        if (group.getStatus() == MarkStatus.FORMAL) {
+            libraryDao.resetByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber(), LibraryStatus.WAITING);
+            arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+            trackDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+            specialTagDao.deleteByExamAndSubjectAndGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        } else if (group.getStatus() == MarkStatus.TRIAL) {
+            trialHistoryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+        }
         releaseByGroup(group);
     }
 

+ 76 - 4
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/TaskServiceImpl.java

@@ -29,6 +29,8 @@ import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.biz.mark.model.SpecialTagDTO;
 import cn.com.qmth.stmms.biz.mark.model.Task;
 import cn.com.qmth.stmms.biz.mark.model.TrackDTO;
+import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
 import cn.com.qmth.stmms.biz.mark.query.MarkLibrarySearchQuery;
 import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
 import cn.com.qmth.stmms.biz.mark.service.MarkSpecialTagService;
@@ -128,7 +130,7 @@ public class TaskServiceImpl implements TaskService {
         Task task = new Task();
         task.setTagList(getMarkSpecialTagList(library.getId()));
         task.setExist(true);
-        task.setStudentId(library.getExamNumber());
+        task.setStudentId(library.getStudentId().toString());
         task.setLibraryId(library.getId());
         task.setMarkStepList(buildMarkStep(group, library.getId()));
         task.setPictureUrls(PictureUrlBuilder.getSliceUrls(library.getExamId(), library.getCampusId(),
@@ -141,9 +143,6 @@ public class TaskServiceImpl implements TaskService {
         task.setObjectiveScore(student != null ? student.getObjectiveScore() : 0);
         task.setMarkTime(library.getMarkerTime());
         task.setTags(library.getTags());
-        if (library.getMarkerId() != null) {
-            task.setMarkId(library.getMarkerId());
-        }
         if (library.getMarkerScore() != null) {
             task.setTotalScore(library.getMarkerScore());
         }
@@ -157,6 +156,32 @@ public class TaskServiceImpl implements TaskService {
         return task;
     }
 
+    @Override
+    public Task build(TrialLibrary library, TrialHistory history) {
+        ExamStudent student = studentService.findById(library.getStudentId());
+        MarkGroup group = groupService.findOne(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
+        Task task = new Task();
+        task.setExist(true);
+        task.setStudentId(library.getStudentId().toString());
+        task.setLibraryId(library.getId());
+        task.setMarkStepList(buildTrialStep(group, history));
+        task.setPictureUrls(PictureUrlBuilder.getSliceUrls(library.getExamId(), library.getCampusId(),
+                library.getSubjectCode(), library.getExamNumber(), student.getSliceCount()));
+        task.setPictureConfig(group.getPictureConfigList());
+        task.setSheetUrls(PictureUrlBuilder.getSheetUrls(library.getExamId(), library.getCampusId(),
+                library.getSubjectCode(), library.getExamNumber(), student.getSheetCount()));
+        task.setAnswerUrl(PictureUrlBuilder.getAnswerUrl(library.getExamId(), library.getSubjectCode()));
+        task.setPaperUrl(PictureUrlBuilder.getPaperUrl(library.getExamId(), library.getSubjectCode()));
+        task.setObjectiveScore(student != null ? student.getObjectiveScore() : 0);
+        if (history != null) {
+            task.setMarkTime(history.getMarkerTime());
+            task.setTotalScore(history.getMarkerScore());
+            task.setScoreList(history.getMarkerScoreList());
+            task.setTagList(getTrialTagList(library.getId()));
+        }
+        return task;
+    }
+
     private List<MarkStepDTO> buildMarkStep(MarkGroup group, Integer libraryId) {
         List<MarkStepDTO> list = new LinkedList<MarkStepDTO>();
         List<ExamQuestion> sList = questionService.findByExamAndSubjectAndPaperTypeAndObjectiveAndMainNumber(
@@ -186,6 +211,36 @@ public class TaskServiceImpl implements TaskService {
         return list;
     }
 
+    private List<MarkStepDTO> buildTrialStep(MarkGroup group, TrialHistory history) {
+        List<MarkStepDTO> list = new LinkedList<MarkStepDTO>();
+        List<ExamQuestion> sList = questionService.findByExamAndSubjectAndPaperTypeAndObjectiveAndMainNumber(
+                group.getExamId(), group.getSubjectCode(), null, false, group.getNumber());
+        int number = 0;
+        for (ExamQuestion question : sList) {
+            number++;
+
+            MarkStepDTO step = new MarkStepDTO();
+            step.setQuestionNumber(question.getQuestionNumber());
+            step.setBlockId(question.getMainNumber());
+            step.setNumber(number);
+            step.setTitle(question.getMainTitle() + "-" + question.getSubNumber());
+            step.setTotalScore(question.getTotalScore());
+            step.setDefaultScore(0d);
+            step.setHasLevel(false);
+            step.setMax(question.getTotalScore());
+            step.setMin(0d);
+            step.setInterval(question.getIntervalScore());
+            step.setScoreList(question.getScoreListArray());
+            if (history != null) {
+                // TODO 增加试评阅卷轨迹列表获取
+                // addTrack(step, question,
+                // trackService.findByLibraryId(libraryId));
+            }
+            list.add(step);
+        }
+        return list;
+    }
+
     private void addTrack(MarkStepDTO step, ExamQuestion question, List<MarkTrack> trackList) {
         String questionNumber = question.getQuestionNumber();
         for (MarkTrack track : trackList) {
@@ -211,6 +266,23 @@ public class TaskServiceImpl implements TaskService {
         return specialTags;
     }
 
+    public SpecialTagDTO[] getTrialTagList(Integer libraryId) {
+        // TODO 需要补充试评特殊标记
+        SpecialTagDTO[] specialTags = null;
+        List<MarkSpecialTag> list = markSpecialTagService.findByLibraryId(libraryId);
+        if (!list.isEmpty()) {
+            specialTags = new SpecialTagDTO[list.size()];
+            for (int i = 0; i < list.size(); i++) {
+                SpecialTagDTO specialTagDTO = new SpecialTagDTO();
+                specialTagDTO.setTagName(list.get(i).getTagName());
+                specialTagDTO.setPositionX(list.get(i).getPositionX());
+                specialTagDTO.setPositionY(list.get(i).getPositionY());
+                specialTags[i] = specialTagDTO;
+            }
+        }
+        return specialTags;
+    }
+
     @Override
     public Task build(Integer studentId) {
         ExamStudent student = studentService.findById(studentId);

+ 128 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/TrialServiceImpl.java

@@ -0,0 +1,128 @@
+package cn.com.qmth.stmms.biz.mark.service.Impl;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.persistence.criteria.CriteriaBuilder;
+import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.data.jpa.domain.Specification;
+import org.springframework.stereotype.Service;
+
+import cn.com.qmth.stmms.biz.common.BaseQuery;
+import cn.com.qmth.stmms.biz.common.BaseQueryService;
+import cn.com.qmth.stmms.biz.mark.dao.TrialHistoryDao;
+import cn.com.qmth.stmms.biz.mark.dao.TrialLibraryDao;
+import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
+import cn.com.qmth.stmms.biz.mark.query.TrialLibrarySearchQuery;
+import cn.com.qmth.stmms.biz.mark.service.TrialService;
+
+@Service
+public class TrialServiceImpl extends BaseQueryService<TrialLibrary> implements TrialService {
+
+    @Autowired
+    private TrialLibraryDao libraryDao;
+
+    @Autowired
+    private TrialHistoryDao historyDao;
+
+    @Override
+    public TrialLibrary findLibrary(Integer id) {
+        return libraryDao.findOne(id);
+    }
+
+    @Override
+    public List<TrialLibrary> findUnMarkedLibrary(Integer examId, String subjectCode, Integer groupNumber,
+            Integer markerId, int pageNumber, int pageSize) {
+        TrialLibrarySearchQuery query = new TrialLibrarySearchQuery();
+        query.setPageNumber(pageNumber);
+        query.setPageSize(pageSize);
+
+        return libraryDao.findUnMarked(examId, subjectCode, groupNumber, markerId, query);
+    }
+
+    @Override
+    public TrialLibrarySearchQuery findLibrary(final TrialLibrarySearchQuery query) {
+        checkQuery(query);
+        Page<TrialLibrary> result = libraryDao.findAll(buildSpecification(query), query);
+        fillResult(result, query);
+        return query;
+    }
+
+    @Override
+    public long countLibrary(Integer examId, String subjectCode, Integer groupNumber) {
+        return libraryDao.countByExamIdAndSubjectCodeAndGroupNumber(examId, subjectCode, groupNumber);
+    }
+
+    @Override
+    public long countMarkedLibrary(Integer examId, String subjectCode, Integer groupNumber) {
+        return libraryDao.countMarked(examId, subjectCode, groupNumber);
+    }
+
+    @Override
+    public List<TrialHistory> findHistory(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
+            int pageNumber, int pageSize) {
+        BaseQuery<TrialHistory> query = new BaseQuery<>();
+        query.setPageNumber(pageNumber);
+        query.setPageSize(pageSize);
+        query.setSort(new Sort(Direction.DESC, "markerTime"));
+        return historyDao.findByExamIdAndSubjectCodeAndGroupNumberAndMarkerId(examId, subjectCode, groupNumber,
+                markerId, query);
+    }
+
+    @Override
+    public List<TrialHistory> findHistory(Integer libraryId) {
+        return historyDao.findByPkLibraryId(libraryId);
+    }
+
+    @Override
+    public TrialHistory findHistory(Integer libraryId, Integer markerId) {
+        return historyDao.findByPkLibraryIdAndPkMarkerId(libraryId, markerId);
+    }
+
+    @Override
+    public long countHistory(Integer libraryId) {
+        return historyDao.countByLibraryId(libraryId);
+    }
+
+    @Override
+    public long countMarkerHistory(Integer markerId) {
+        return historyDao.countByMarkerId(markerId);
+    }
+
+    private Specification<TrialLibrary> buildSpecification(final TrialLibrarySearchQuery query) {
+        return new Specification<TrialLibrary>() {
+
+            @Override
+            public Predicate toPredicate(Root<TrialLibrary> root, CriteriaQuery<?> cQuery, CriteriaBuilder cb) {
+                List<Predicate> predicates = new LinkedList<Predicate>();
+                if (query.getExamId() != null) {
+                    predicates.add(cb.equal(root.get("examId"), query.getExamId()));
+                }
+                if (StringUtils.isNotBlank(query.getSubjectCode())) {
+                    predicates.add(cb.equal(root.get("subjectCode"), query.getSubjectCode()));
+                }
+                if (query.getGroupNumber() != null) {
+                    predicates.add(cb.equal(root.get("groupNumber"), query.getGroupNumber()));
+                }
+                if (StringUtils.isNotBlank(query.getExamNumber())) {
+                    predicates.add(cb.equal(root.get("examNumber"), query.getExamNumber()));
+                }
+                if (query.getStudentId() != null) {
+                    predicates.add(cb.equal(root.get("studentId"), query.getStudentId()));
+                }
+                return predicates.isEmpty() ? cb.conjunction()
+                        : cb.and(predicates.toArray(new Predicate[predicates.size()]));
+            }
+        };
+    }
+
+}

+ 56 - 24
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/MarkService.java

@@ -8,21 +8,28 @@ import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.model.Marker;
 import cn.com.qmth.stmms.biz.mark.model.ArbitrateHistory;
 import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
-import cn.com.qmth.stmms.biz.mark.model.MarkSpecialTag;
-import cn.com.qmth.stmms.biz.mark.model.MarkTrack;
-import cn.com.qmth.stmms.biz.user.model.User;
+import cn.com.qmth.stmms.biz.mark.model.MarkResult;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
 import cn.com.qmth.stmms.common.enums.ScorePolicy;
 
 public interface MarkService {
 
     /**
-     * 释放某个评卷员已领取的任务
+     * 释放某个评卷员已领取的正评任务
      * 
      * @param library
      * @param marker
      */
     void releaseLibrary(MarkLibrary library, Marker marker);
 
+    /**
+     * 释放某个评卷员已领取的试评任务
+     * 
+     * @param library
+     * @param marker
+     */
+    void releaseLibrary(TrialLibrary library, Marker marker);
+
     /**
      * /** 释放某个大题的锁定任务
      * 
@@ -52,7 +59,7 @@ public interface MarkService {
     void updateGroup(MarkGroup group, List<Double> scores, ScorePolicy policy);
 
     /**
-     * 评卷员申请领取某个评卷任务
+     * 评卷员申请领取某个正式评卷任务
      * 
      * @param library
      * @param marker
@@ -60,7 +67,16 @@ public interface MarkService {
     boolean applyLibrary(MarkLibrary library, Marker marker);
 
     /**
-     * 评卷员是否已领取某个评卷任务
+     * 评卷员申请领取某个试评评卷任务
+     * 
+     * @param library
+     * @param marker
+     * @return
+     */
+    boolean applyLibrary(TrialLibrary library, Marker marker);
+
+    /**
+     * 评卷员是否已领取某个正式评卷任务
      * 
      * @param library
      * @param marker
@@ -68,6 +84,15 @@ public interface MarkService {
      */
     boolean hasApplied(MarkLibrary library, Marker marker);
 
+    /**
+     * 评卷员是否已领取某个试评评卷任务
+     * 
+     * @param library
+     * @param marker
+     * @return
+     */
+    boolean hasApplied(TrialLibrary library, Marker marker);
+
     /**
      * 释放某个评卷员的所有锁定任务
      * 
@@ -106,30 +131,19 @@ public interface MarkService {
     void processArbitrate(ArbitrateHistory history);
 
     /**
-     * 评卷员提交某个评卷任务
+     * 管理员/组长打回某个评卷任务
      * 
      * @param library
-     * @param trackList
-     * @param tagList
      * @return
      */
-    boolean submitLibrary(MarkLibrary library, Marker marker, List<MarkTrack> trackList, List<MarkSpecialTag> tagList);
-
-    /**
-     * 管理员/组长直接对考生打分
-     * 
-     * @param student
-     * @param user
-     */
-    void submitStudent(ExamStudent student, User user);
+    boolean backLibrary(MarkLibrary library);
 
     /**
-     * 管理员/组长打回某个评卷任务
+     * 更新某个大题评卷任务数量
      * 
-     * @param library
-     * @return
+     * @param group
      */
-    boolean backLibrary(MarkLibrary library);
+    void updateLibraryCount(MarkGroup group);
 
     /**
      * 更新某个大题评卷任务数量
@@ -166,12 +180,30 @@ public interface MarkService {
     int applyCount(Marker marker);
 
     /**
-     * 根据考生、学习中心、大题构造评卷任务
+     * 根据考生、学习中心、大题构造正式评卷任务
      * 
      * @param student
      * @param campus
      * @param group
      */
-    void buildLibrary(ExamStudent student, Campus campus, MarkGroup group);
+    void buildFormalLibrary(ExamStudent student, Campus campus, MarkGroup group);
+
+    /**
+     * 根据考生、学习中心、大题构造试评评卷任务
+     * 
+     * @param student
+     * @param campus
+     * @param group
+     */
+    void buildTrialLibrary(ExamStudent student, Campus campus, MarkGroup group);
+
+    /**
+     * 评卷员提交评卷任务
+     * 
+     * @param task
+     * @param marker
+     * @return
+     */
+    boolean submitTask(MarkResult result, Marker marker);
 
 }

+ 4 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/TaskService.java

@@ -6,6 +6,8 @@ import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.mark.model.ArbitrateHistory;
 import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
 import cn.com.qmth.stmms.biz.mark.model.Task;
+import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
 import cn.com.qmth.stmms.biz.mark.query.MarkLibrarySearchQuery;
 
 public interface TaskService {
@@ -14,6 +16,8 @@ public interface TaskService {
 
     Task build(ArbitrateHistory history, MarkGroup group);
 
+    Task build(TrialLibrary library, TrialHistory history);
+
     Task build(MarkLibrary library);
 
     Task build(Integer studentId);

+ 33 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/TrialService.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.stmms.biz.mark.service;
+
+import java.util.List;
+
+import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
+import cn.com.qmth.stmms.biz.mark.query.TrialLibrarySearchQuery;
+
+public interface TrialService {
+
+    TrialLibrary findLibrary(Integer id);
+
+    TrialLibrarySearchQuery findLibrary(TrialLibrarySearchQuery query);
+
+    List<TrialLibrary> findUnMarkedLibrary(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
+            int pageNumber, int pageSize);
+
+    long countLibrary(Integer examId, String subjectCode, Integer groupNumber);
+
+    long countMarkedLibrary(Integer examId, String subjectCode, Integer groupNumber);
+
+    List<TrialHistory> findHistory(Integer libraryId);
+
+    TrialHistory findHistory(Integer libraryId, Integer markerId);
+
+    long countHistory(Integer libraryId);
+
+    long countMarkerHistory(Integer markerId);
+
+    List<TrialHistory> findHistory(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
+            int pageNumber, int pageSize);
+
+}

+ 206 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/utils/TrialTaskUtil.java

@@ -0,0 +1,206 @@
+package cn.com.qmth.stmms.biz.utils;
+
+import java.text.ParseException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multiset;
+import com.google.common.collect.SetMultimap;
+
+import cn.com.qmth.stmms.biz.exam.model.Marker;
+
+/**
+ * Redis zset替代方案,单JVM内部针对已领取未给分<strong>试评</strong>任务的内存锁
+ * 
+ * @author luoshi
+ * 
+ */
+public class TrialTaskUtil {
+
+    private static SetMultimap<String, TaskEntry> taskMap = HashMultimap.create();
+
+    /**
+     * 尝试领取某个任务
+     * 
+     * @param marker
+     * @param taskId
+     * @return
+     */
+    public static boolean add(Marker marker, String taskId) {
+        String key = getKey(marker);
+        TaskEntry obj = new TaskEntry(marker.getId(), taskId);
+
+        synchronized (TrialTaskUtil.class) {
+            if (taskMap.containsEntry(key, obj)) {
+                return false;
+            } else {
+                taskMap.put(key, obj);
+                return true;
+            }
+        }
+    }
+
+    /**
+     * 是否已领取某个任务
+     * 
+     * @param marker
+     * @param taskId
+     * @return
+     */
+    public static boolean exists(Marker marker, String taskId) {
+        String key = getKey(marker);
+        Set<TaskEntry> set = taskMap.get(key);
+        if (set != null) {
+            for (TaskEntry entry : set) {
+                if (entry.getTaskId().equals(taskId) && entry.getMarkerId() == marker.getId()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 删除某个已完成的任务
+     * 
+     * @param marker
+     * @param taskId
+     */
+    public static void remove(Marker marker, String taskId) {
+        TaskEntry obj = new TaskEntry(marker.getId(), taskId);
+        synchronized (TrialTaskUtil.class) {
+            taskMap.remove(getKey(marker), obj);
+        }
+    }
+
+    /**
+     * 某个科目已领取未给分的任务总数
+     * 
+     * @param examId
+     * @param subjectCode
+     * @return
+     */
+    public static int count(int examId, String subjectCode, int number) {
+        Set<TaskEntry> set = taskMap.get(getKey(examId, subjectCode, number));
+        return set != null ? set.size() : 0;
+    }
+
+    /**
+     * 某个评卷员已领取未给分的任务总数
+     * 
+     * @param marker
+     * @return
+     */
+    public static int count(Marker marker) {
+        Set<TaskEntry> set = taskMap.get(getKey(marker));
+        int count = 0;
+        if (set != null) {
+            for (TaskEntry obj : set) {
+                if (obj.getMarkerId() == marker.getId()) {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+
+    /**
+     * 清除某个科目已领取未给分的任务
+     * 
+     * @param examId
+     * @param subjectCode
+     */
+    public static void clear(int examId, String subjectCode, int number) {
+        synchronized (TrialTaskUtil.class) {
+            taskMap.removeAll(getKey(examId, subjectCode, number));
+        }
+    }
+
+    /**
+     * 清除某个评卷员已领取未给分的任务
+     * 
+     * @param marker
+     */
+    public static void clear(Marker marker) {
+        String key = getKey(marker);
+        Set<TaskEntry> set = new HashSet<TaskEntry>();
+        synchronized (TrialTaskUtil.class) {
+            set.addAll(taskMap.get(key));
+            for (TaskEntry obj : set) {
+                if (obj.getMarkerId() == marker.getId()) {
+                    taskMap.remove(key, obj);
+                }
+            }
+        }
+    }
+
+    /**
+     * 获取当前已领取的任务快照
+     * 
+     * @param examId
+     * @param subjectCode
+     * @param groupNumber
+     */
+    public static Set<TaskEntry> list(Integer examId, String subjectCode, Integer groupNumber) {
+        return taskMap.get(getKey(examId, subjectCode, groupNumber));
+    }
+
+    private static String getKey(int examId, String subjectCode, int number) {
+        return examId + "_" + subjectCode + "_" + number;
+    }
+
+    private static String getKey(Marker marker) {
+        return getKey(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+    }
+
+    public static void clearTimeoutTask(long timeoutMinute) {
+        synchronized (TrialTaskUtil.class) {
+            SetMultimap<String, TaskEntry> taskMap1 = HashMultimap.create();
+            // System.out.println("任务池大小:"+taskMap.size());
+            // System.out.println("间隔时间:"+cleanMapinterval);
+            if (taskMap.size() > 0) {
+                Multiset<String> keysTemp = taskMap.keys();
+                Set<String> setKey = new HashSet<String>();
+                for (String key : keysTemp) {
+                    setKey.add(key);
+                }
+                for (String key : setKey) {
+                    Set<TaskEntry> set = taskMap.get(key);
+                    if (set != null) {
+                        for (TaskEntry obj : set) {
+                            if (getDateDifference(obj.getTimestamp()) >= timeoutMinute) {// 如果相隔时间超过阀值,则该试卷的放入清空池中
+                                taskMap1.put(key, obj);
+                            }
+                        }
+                    }
+                }
+            }
+            for (Map.Entry<String, TaskEntry> taskEntry : taskMap1.entries()) {
+                taskMap.remove(taskEntry.getKey(), taskEntry.getValue());
+            }
+            taskMap1.clear();
+        }
+    }
+
+    /**
+     * 获取当前时间戳
+     * 
+     * @return
+     * @throws ParseException
+     */
+    public static Long getDateString() {
+        return System.currentTimeMillis();
+    }
+
+    /**
+     * 时间戳相隔分钟
+     * 
+     * @return
+     * @throws ParseException
+     */
+    public static Long getDateDifference(Long oldTime) {
+        return (getDateString() - oldTime) / (1000 * 60);
+    }
+}

+ 0 - 34
stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/MarkStage.java

@@ -1,34 +0,0 @@
-package cn.com.qmth.stmms.common.enums;
-
-public enum MarkStage {
-
-    WAITING("未开始", 0), EXAMPLE("样卷", 1), TEST("试评", 2), FORMAL("正评", 3), FINISH("评卷结束", 4);
-
-    private String name;
-
-    private int value;
-
-    private MarkStage(String name, int value) {
-        this.name = name;
-        this.value = value;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public int getValue() {
-        return value;
-    }
-
-    public static MarkStage findByValue(int value) {
-        MarkStage status = null;
-        for (MarkStage s : MarkStage.values()) {
-            if (s.getValue() == value) {
-                status = s;
-                break;
-            }
-        }
-        return status;
-    }
-}

+ 32 - 0
stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/MarkStatus.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.stmms.common.enums;
+
+public enum MarkStatus {
+
+    TRIAL("试评", 1), FORMAL("正评", 2), FINISH("结束", 3);
+
+    private String name;
+
+    private int value;
+
+    private MarkStatus(String name, int value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public static MarkStatus findByName(String name) {
+        for (MarkStatus c : MarkStatus.values()) {
+            if (c.toString().equalsIgnoreCase(name)) {
+                return c;
+            }
+        }
+        return null;
+    }
+}

+ 157 - 157
stmms-common/src/main/java/cn/com/qmth/stmms/common/redis/RedisKeyBuilder.java

@@ -1,157 +1,157 @@
-package cn.com.qmth.stmms.common.redis;
-
-import java.text.MessageFormat;
-
-import cn.com.qmth.stmms.common.enums.LibraryType;
-import cn.com.qmth.stmms.common.enums.MarkStage;
-import org.apache.commons.lang.StringUtils;
-
-public class RedisKeyBuilder {
-
-    private static final String MARK_CURRENT_TASK_KEY = "mark:current:{0}:{1}:{2}";
-
-    private static final String MARK_PUBLIC_TASK_KEY = "mark:public:{0}:{1}";
-
-    private static final String MARK_STANDARD_TASK_KEY = "mark:private:{0}:{1}";
-
-    private static final String MARK_BACK_TASK_KEY = "mark:back:{0}:{1}";
-
-    private static final String MARK_SELF_TASK_KEY = "mark:self:{0}:{1}";
-
-    private static final String MARK_COUNT_KEY = "mark:count:{0}:{1}";
-
-    private static final String BLOCK_TOTAL_COUNT_KEY = "block:total:count:{0}";
-
-    private static final String BLOCK_MARKED_TASK_KEY = "block:marked:task:{0}";
-
-    private static final String MARK_CURRENT_TASK_TIME_KEY = "mark:current:time:key:{0}:{1}:{2}";
-
-    /**
-     * 已领取未给分的评卷任务
-     * 
-     * @param examId
-     * @param schoolId
-     * @param blockId
-     * @return
-     */
-    public static String getCurrentTaskKey(int examId, String subjectCode, int number) {
-        return MessageFormat.format(MARK_CURRENT_TASK_KEY, String.valueOf(examId), subjectCode, String.valueOf(number));
-    }
-
-    /**
-     * 公共评卷任务
-     * 
-     * @param examId
-     * @param schoolId
-     * @param blockId
-     * @return
-     */
-    public static String getPublicTaskKey(LibraryType libraryType, int blockId) {
-        return MessageFormat.format(MARK_PUBLIC_TASK_KEY, String.valueOf(libraryType.getValue()),
-                String.valueOf(blockId));
-    }
-
-    /**
-     * 个人标准卷任务
-     * 
-     * @param examId
-     * @param schoolId
-     * @param blockId
-     * @return
-     */
-    public static String getStandardTaskKey(MarkStage stage, int markerId) {
-        return MessageFormat.format(MARK_STANDARD_TASK_KEY, String.valueOf(stage.getValue()), String.valueOf(markerId));
-    }
-
-    /**
-     * 个人打回任务
-     * 
-     * @param examId
-     * @param schoolId
-     * @param blockId
-     * @return
-     */
-    public static String getBackTaskKey(LibraryType libraryType, int markerId) {
-        return MessageFormat.format(MARK_BACK_TASK_KEY, String.valueOf(libraryType.getValue()),
-                String.valueOf(markerId));
-    }
-
-    /**
-     * 个人自评任务
-     * 
-     * @param examId
-     * @param schoolId
-     * @param blockId
-     * @return
-     */
-    public static String getSelfTaskKey(LibraryType libraryType, int markerId) {
-        return MessageFormat.format(MARK_SELF_TASK_KEY, String.valueOf(libraryType.getValue()),
-                String.valueOf(markerId));
-    }
-
-    /**
-     * 个人评卷数量
-     * 
-     * @param examId
-     * @param schoolId
-     * @param markerId
-     * @return
-     */
-    public static String getMarkCountKey(MarkStage stage, int markerId) {
-        return MessageFormat.format(MARK_COUNT_KEY, String.valueOf(stage.getValue()), String.valueOf(markerId));
-    }
-
-    /**
-     * 评卷任务总量
-     * 
-     * @param blockId
-     * @return
-     */
-    public static String getBlockTotalCountKey(int blockId) {
-        return MessageFormat.format(BLOCK_TOTAL_COUNT_KEY, String.valueOf(blockId));
-    }
-
-    /**
-     * 未评任务总量
-     * 
-     * @param blockId
-     * @return
-     */
-    // public static String getBlockUnmarkCountKey(LibraryType type, int
-    // blockId) {
-    // return MessageFormat.format(BLOCK_UNMARK_COUNT_KEY, type.getValue(),
-    // blockId);
-    // }
-
-    /**
-     * 已评任务集合
-     * 
-     * @param blockId
-     * @return
-     */
-    public static String getBlockMarkedTaskKey(int blockId) {
-        return MessageFormat.format(BLOCK_MARKED_TASK_KEY, String.valueOf(blockId));
-    }
-    /**
-     * 已领取未给分的评卷任务的领取时间的Key
-     *
-     * @param examId
-     * @param subjectCode
-     * @return
-     */
-    public static String getCurrentTaskTimeKey(int examId, String subjectCode,int number) {
-        return MessageFormat.format(MARK_CURRENT_TASK_TIME_KEY, String.valueOf(examId), subjectCode,number);
-    }
-
-
-    public static String getCurrentTaskKeyByTimeKey(String TimeKey){
-        if(!StringUtils.isBlank(TimeKey)){
-            return  TimeKey.replace("mark:current:time:key", "mark:current");
-        }
-        return "";
-    }
-
-    public static String getCurrentTaskTimePrefix(){
-        return MARK_CURRENT_TASK_TIME_KEY;
-    }
-}
+package cn.com.qmth.stmms.common.redis;
+
+import java.text.MessageFormat;
+
+import cn.com.qmth.stmms.common.enums.LibraryType;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
+import org.apache.commons.lang.StringUtils;
+
+public class RedisKeyBuilder {
+
+    private static final String MARK_CURRENT_TASK_KEY = "mark:current:{0}:{1}:{2}";
+
+    private static final String MARK_PUBLIC_TASK_KEY = "mark:public:{0}:{1}";
+
+    private static final String MARK_STANDARD_TASK_KEY = "mark:private:{0}:{1}";
+
+    private static final String MARK_BACK_TASK_KEY = "mark:back:{0}:{1}";
+
+    private static final String MARK_SELF_TASK_KEY = "mark:self:{0}:{1}";
+
+    private static final String MARK_COUNT_KEY = "mark:count:{0}:{1}";
+
+    private static final String BLOCK_TOTAL_COUNT_KEY = "block:total:count:{0}";
+
+    private static final String BLOCK_MARKED_TASK_KEY = "block:marked:task:{0}";
+
+    private static final String MARK_CURRENT_TASK_TIME_KEY = "mark:current:time:key:{0}:{1}:{2}";
+
+    /**
+     * 已领取未给分的评卷任务
+     * 
+     * @param examId
+     * @param schoolId
+     * @param blockId
+     * @return
+     */
+    public static String getCurrentTaskKey(int examId, String subjectCode, int number) {
+        return MessageFormat.format(MARK_CURRENT_TASK_KEY, String.valueOf(examId), subjectCode, String.valueOf(number));
+    }
+
+    /**
+     * 公共评卷任务
+     * 
+     * @param examId
+     * @param schoolId
+     * @param blockId
+     * @return
+     */
+    public static String getPublicTaskKey(LibraryType libraryType, int blockId) {
+        return MessageFormat.format(MARK_PUBLIC_TASK_KEY, String.valueOf(libraryType.getValue()),
+                String.valueOf(blockId));
+    }
+
+    /**
+     * 个人标准卷任务
+     * 
+     * @param examId
+     * @param schoolId
+     * @param blockId
+     * @return
+     */
+    public static String getStandardTaskKey(MarkStatus stage, int markerId) {
+        return MessageFormat.format(MARK_STANDARD_TASK_KEY, String.valueOf(stage.getValue()), String.valueOf(markerId));
+    }
+
+    /**
+     * 个人打回任务
+     * 
+     * @param examId
+     * @param schoolId
+     * @param blockId
+     * @return
+     */
+    public static String getBackTaskKey(LibraryType libraryType, int markerId) {
+        return MessageFormat.format(MARK_BACK_TASK_KEY, String.valueOf(libraryType.getValue()),
+                String.valueOf(markerId));
+    }
+
+    /**
+     * 个人自评任务
+     * 
+     * @param examId
+     * @param schoolId
+     * @param blockId
+     * @return
+     */
+    public static String getSelfTaskKey(LibraryType libraryType, int markerId) {
+        return MessageFormat.format(MARK_SELF_TASK_KEY, String.valueOf(libraryType.getValue()),
+                String.valueOf(markerId));
+    }
+
+    /**
+     * 个人评卷数量
+     * 
+     * @param examId
+     * @param schoolId
+     * @param markerId
+     * @return
+     */
+    public static String getMarkCountKey(MarkStatus stage, int markerId) {
+        return MessageFormat.format(MARK_COUNT_KEY, String.valueOf(stage.getValue()), String.valueOf(markerId));
+    }
+
+    /**
+     * 评卷任务总量
+     * 
+     * @param blockId
+     * @return
+     */
+    public static String getBlockTotalCountKey(int blockId) {
+        return MessageFormat.format(BLOCK_TOTAL_COUNT_KEY, String.valueOf(blockId));
+    }
+
+    /**
+     * 未评任务总量
+     * 
+     * @param blockId
+     * @return
+     */
+    // public static String getBlockUnmarkCountKey(LibraryType type, int
+    // blockId) {
+    // return MessageFormat.format(BLOCK_UNMARK_COUNT_KEY, type.getValue(),
+    // blockId);
+    // }
+
+    /**
+     * 已评任务集合
+     * 
+     * @param blockId
+     * @return
+     */
+    public static String getBlockMarkedTaskKey(int blockId) {
+        return MessageFormat.format(BLOCK_MARKED_TASK_KEY, String.valueOf(blockId));
+    }
+    /**
+     * 已领取未给分的评卷任务的领取时间的Key
+     *
+     * @param examId
+     * @param subjectCode
+     * @return
+     */
+    public static String getCurrentTaskTimeKey(int examId, String subjectCode,int number) {
+        return MessageFormat.format(MARK_CURRENT_TASK_TIME_KEY, String.valueOf(examId), subjectCode,number);
+    }
+
+
+    public static String getCurrentTaskKeyByTimeKey(String TimeKey){
+        if(!StringUtils.isBlank(TimeKey)){
+            return  TimeKey.replace("mark:current:time:key", "mark:current");
+        }
+        return "";
+    }
+
+    public static String getCurrentTaskTimePrefix(){
+        return MARK_CURRENT_TASK_TIME_KEY;
+    }
+}

+ 2 - 1
stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/SubjectQuestionDTO.java

@@ -64,7 +64,8 @@ public class SubjectQuestionDTO {
                 if (group == null) {
                     group = new MarkGroup(examId, subjectCode, question.getMainNumber(), question.getMainTitle(),
                             PictureConfigItem.parse(question.getPicList()), 0d, question.getDoubleRate(),
-                            question.getArbitrateThreshold(), question.getScorePolicy(), question.getMarkMode());
+                            question.getArbitrateThreshold(), question.getScorePolicy(), question.getMarkMode(),
+                            question.getTrialCount());
                     group.setQuestionList(new LinkedList<ExamQuestion>());
                     groups.put(question.getMainNumber(), group);
                 }

+ 13 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/SubjectiveQuestionDTO.java

@@ -46,6 +46,9 @@ public class SubjectiveQuestionDTO implements QuestionDTO {
     @ExcelField(title = "评卷模式(common-普通,track-轨迹)", align = 2, sort = 120)
     private String markMode;
 
+    @ExcelField(title = "试评数量(0-跳过试评)", align = 2, sort = 130)
+    private Integer trialCount;
+
     public SubjectiveQuestionDTO() {
 
     }
@@ -65,6 +68,7 @@ public class SubjectiveQuestionDTO implements QuestionDTO {
         setScorePolicy(group != null && group.getScorePolicy() != null ? group.getScorePolicy().getValue()
                 : ScorePolicy.AVG.getValue());
         setMarkMode(group != null && group.getMarkMode() != null ? group.getMarkMode().getName() : "");
+        setTrialCount(question.getTrialCount());
     }
 
     public ExamQuestion transform() {
@@ -84,6 +88,7 @@ public class SubjectiveQuestionDTO implements QuestionDTO {
         question.setArbitrateThreshold(arbitrateThreshold);
         question.setScorePolicy(scorePolicy);
         question.setMarkMode(markMode);
+        question.setTrialCount(trialCount);
         return question;
     }
 
@@ -196,4 +201,12 @@ public class SubjectiveQuestionDTO implements QuestionDTO {
         this.markMode = markMode;
     }
 
+    public Integer getTrialCount() {
+        return trialCount;
+    }
+
+    public void setTrialCount(Integer trialCount) {
+        this.trialCount = trialCount;
+    }
+
 }

+ 41 - 5
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkGroupController.java

@@ -39,6 +39,7 @@ import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.common.auth.annotation.RoleRequire;
 import cn.com.qmth.stmms.common.domain.WebUser;
 import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.Role;
 import cn.com.qmth.stmms.common.enums.ScorePolicy;
 import cn.com.qmth.stmms.common.utils.PictureUrlBuilder;
@@ -155,7 +156,6 @@ public class MarkGroupController extends BaseExamController {
     }
 
     @RequestMapping("/reset")
-    @Transactional
     @RoleRequire(Role.SCHOOL_ADMIN)
     public String reset(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
             @RequestParam String subjectCode, @RequestParam Integer number) {
@@ -170,6 +170,37 @@ public class MarkGroupController extends BaseExamController {
         return "redirect:/admin/exam/group";
     }
 
+    @RequestMapping("/changeStatus")
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public String changeStatus(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number, @RequestParam MarkStatus status) {
+        int examId = getSessionExamId(request);
+        MarkGroup group = groupService.findOne(examId, subjectCode, number);
+        if (group == null) {
+            return "redirect:/admin/exam/mark";
+        }
+        boolean allow = false;
+        if (group.getStatus() == MarkStatus.TRIAL && status == MarkStatus.FORMAL) {
+            allow = true;
+        } else if (group.getStatus() == MarkStatus.FORMAL && status == MarkStatus.FINISH && group.getLeftCount() == 0) {
+            allow = true;
+        } else if (group.getStatus() == MarkStatus.FINISH && status == MarkStatus.FORMAL) {
+            allow = true;
+        }
+        if (allow) {
+            if (groupService.updateStatus(examId, subjectCode, number, status, group.getStatus())
+                    && status == MarkStatus.FORMAL) {
+                // 切换到正评成功后刷新任务数量
+                group.setStatus(status);
+                markService.updateLibraryCount(group);
+            }
+        } else {
+            redirectAttributes.addAttribute("message", "不能切换到指定的评卷状态");
+        }
+        redirectAttributes.addAttribute("subjectCode", subjectCode);
+        return "redirect:/admin/exam/group";
+    }
+
     @RequestMapping("/add")
     @RoleRequire(Role.SCHOOL_ADMIN)
     public String editSimple(HttpServletRequest request, Model model, @RequestParam String subjectCode) {
@@ -254,7 +285,8 @@ public class MarkGroupController extends BaseExamController {
             @RequestParam(required = false) String intervalScoreList, @RequestParam(required = false) String scoreList,
             @RequestParam(required = false) Double doubleRate,
             @RequestParam(required = false) Double arbitrateThreshold,
-            @RequestParam(required = false) Integer scorePolicy, @RequestParam(required = false) String markMode) {
+            @RequestParam(required = false) Integer scorePolicy, @RequestParam(required = false) MarkMode markMode,
+            @RequestParam(required = false) Integer trialCount) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         List<ExamQuestion> questionList = questionService
@@ -287,7 +319,10 @@ public class MarkGroupController extends BaseExamController {
                 groupService.updateArbitrateThreshold(examId, subjectCode, number, arbitrateThreshold);
             }
             if (markMode != null) {
-                groupService.updateMarkMode(examId, subjectCode, number, MarkMode.findByName(markMode));
+                groupService.updateMarkMode(examId, subjectCode, number, markMode);
+            }
+            if (trialCount != null && trialCount > 0 && group.getStatus() == MarkStatus.TRIAL) {
+                groupService.updateTrialCount(examId, subjectCode, number, trialCount);
             }
             // advance update
             ScorePolicy policy = scorePolicy != null ? ScorePolicy.findByValue(scorePolicy) : null;
@@ -315,7 +350,8 @@ public class MarkGroupController extends BaseExamController {
             @RequestParam(required = false) String title, @RequestParam(required = false) String picList,
             @RequestParam(required = false) String scoreList, @RequestParam(required = false) Double doubleRate,
             @RequestParam(required = false) Double arbitrateThreshold,
-            @RequestParam(required = false) Integer scorePolicy, @RequestParam(required = false) String markMode) {
+            @RequestParam(required = false) Integer scorePolicy, @RequestParam(required = false) String markMode,
+            @RequestParam(required = false) Integer trialCount) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         if (group != null) {
@@ -332,7 +368,7 @@ public class MarkGroupController extends BaseExamController {
             JSONArray array = JSONArray.fromObject(picList);
             List<PictureConfigItem> list = JSONArray.toList(array, new PictureConfigItem(), new JsonConfig());
             group = new MarkGroup(examId, subjectCode, number, StringUtils.trimToNull(title), list, 0d, doubleRate,
-                    arbitrateThreshold, scorePolicy, markMode);
+                    arbitrateThreshold, scorePolicy, markMode, trialCount);
             List<Double> scores = buildDoubleList(scoreList);
             if (group.getTitle() != null && group.getPicList() != null && scores.size() > 0) {
                 // clear and replace exam_question

+ 26 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/BaseController.java

@@ -19,6 +19,8 @@ import cn.com.qmth.stmms.biz.utils.ScoreCalculateUtil;
 import cn.com.qmth.stmms.biz.utils.ScoreInfo;
 import cn.com.qmth.stmms.common.enums.ExamSubjectStatus;
 import cn.com.qmth.stmms.common.enums.LibraryStatus;
+import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.Role;
 import cn.com.qmth.stmms.common.utils.DateUtils;
 
@@ -108,6 +110,30 @@ public class BaseController {
                 }
             }
         });
+        // MarkStatus 类型转换
+        binder.registerCustomEditor(MarkStatus.class, new PropertyEditorSupport() {
+
+            @Override
+            public void setAsText(String text) {
+                try {
+                    setValue(MarkStatus.findByName(text));
+                } catch (Exception e) {
+                    setValue(null);
+                }
+            }
+        });
+        // MarkMode 类型转换
+        binder.registerCustomEditor(MarkMode.class, new PropertyEditorSupport() {
+
+            @Override
+            public void setAsText(String text) {
+                try {
+                    setValue(MarkMode.findByName(text));
+                } catch (Exception e) {
+                    setValue(null);
+                }
+            }
+        });
 
         binder.registerCustomEditor(Boolean.class, new CustomBooleanEditor(true));
     }

+ 111 - 67
stmms-web/src/main/java/cn/com/qmth/stmms/mark/MarkController.java

@@ -1,5 +1,6 @@
 package cn.com.qmth.stmms.mark;
 
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
@@ -29,16 +30,21 @@ import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.exam.service.MarkerService;
 import cn.com.qmth.stmms.biz.exam.service.TagService;
 import cn.com.qmth.stmms.biz.mark.model.MarkLibrary;
+import cn.com.qmth.stmms.biz.mark.model.MarkResult;
 import cn.com.qmth.stmms.biz.mark.model.Task;
+import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
+import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
 import cn.com.qmth.stmms.biz.mark.query.MarkLibrarySearchQuery;
 import cn.com.qmth.stmms.biz.mark.service.MarkLibraryService;
 import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.biz.mark.service.TaskService;
+import cn.com.qmth.stmms.biz.mark.service.TrialService;
 import cn.com.qmth.stmms.biz.mark.service.Impl.MarkLockService;
 import cn.com.qmth.stmms.common.controller.BaseController;
 import cn.com.qmth.stmms.common.enums.ExamSubjectStatus;
 import cn.com.qmth.stmms.common.enums.LibraryStatus;
 import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.utils.RequestUtils;
 import net.sf.json.JSONArray;
 import net.sf.json.JSONObject;
@@ -52,6 +58,9 @@ public class MarkController extends BaseController {
     @Autowired
     private ExamSubjectService subjectService;
 
+    @Autowired
+    private TrialService trialService;
+
     @Autowired
     private MarkerService markerService;
 
@@ -190,35 +199,48 @@ public class MarkController extends BaseController {
         JSONObject status = new JSONObject();
         Marker marker = RequestUtils.getWebUser(request).getMarker();
         ExamSubject subject = subjectService.find(marker.getExamId(), marker.getSubjectCode());
-        if (subject == null || subject.getStatus() != ExamSubjectStatus.MARKING) {
+        MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+        if (subject == null || subject.getStatus() != ExamSubjectStatus.MARKING || group == null
+                || group.getStatus() == MarkStatus.FINISH) {
             status.accumulate("valid", false);
             return status;
         }
-
-        MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
-        query.setExamId(marker.getExamId());
-        query.setSubjectCode(marker.getSubjectCode());
-        query.setGroupNumber(marker.getGroupNumber());
-        long totalCount = libraryService.countByQuery(query);
-
-        query.setMarkerId(marker.getId());
-        long personCount = libraryService.countByQuery(query);
-
-        query.setMarkerId(0);
-        query.addStatus(LibraryStatus.MARKED);
-        query.addStatus(LibraryStatus.ARBITRATED);
-        long markedCount = libraryService.countByQuery(query);
-
-        query.clearStatus();
-        query.addStatus(LibraryStatus.WAIT_ARBITRATE);
-        long exceptionCount = libraryService.countByQuery(query);
-
+        long totalCount = 0;
+        long personCount = 0;
+        long markedCount = 0;
+        long exceptionCount = 0;
+        long topCount = 0;
+        if (group.getStatus() == MarkStatus.FORMAL) {
+            topCount = marker.getTopCount() != null ? marker.getTopCount() : 0;
+
+            MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
+            query.setExamId(marker.getExamId());
+            query.setSubjectCode(marker.getSubjectCode());
+            query.setGroupNumber(marker.getGroupNumber());
+            totalCount = libraryService.countByQuery(query);
+
+            query.setMarkerId(marker.getId());
+            personCount = libraryService.countByQuery(query);
+
+            query.setMarkerId(0);
+            query.addStatus(LibraryStatus.MARKED);
+            query.addStatus(LibraryStatus.ARBITRATED);
+            markedCount = libraryService.countByQuery(query);
+
+            query.clearStatus();
+            query.addStatus(LibraryStatus.WAIT_ARBITRATE);
+            exceptionCount = libraryService.countByQuery(query);
+        } else if (group.getStatus() == MarkStatus.TRIAL) {
+            totalCount = trialService.countLibrary(group.getExamId(), group.getSubjectCode(), group.getNumber());
+            personCount = trialService.countMarkerHistory(marker.getId());
+            markedCount = trialService.countMarkedLibrary(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        }
         status.accumulate("personCount", personCount);
         status.accumulate("totalCount", totalCount);
         status.accumulate("markedCount", markedCount);
         status.accumulate("exceptionCount", exceptionCount);
         status.accumulate("valid", totalCount > 0);
-        status.accumulate("topCount", marker.getTopCount() == null ? 0 : marker.getTopCount());
+        status.accumulate("topCount", topCount);
         return status;
     }
 
@@ -227,7 +249,21 @@ public class MarkController extends BaseController {
     public Task getTask(HttpServletRequest request) {
         Marker marker = RequestUtils.getWebUser(request).getMarker();
         lockService.waitUnlockMarker(marker.getId());
-        Task task = getTask(marker);
+        MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getNumber());
+        Task task = null;
+        if (group == null) {
+            task = new Task();
+            task.setExist(false);
+            task.setMessage("评卷大题不存在");
+        } else if (group.getStatus() == MarkStatus.FINISH) {
+            task = new Task();
+            task.setExist(false);
+            task.setMessage("评卷已结束");
+        } else if (group.getStatus() == MarkStatus.TRIAL) {
+            task = getTrialTask(marker);
+        } else if (group.getStatus() == MarkStatus.FORMAL) {
+            task = getFormalTask(marker);
+        }
         if (task == null) {
             task = new Task();
             task.setExist(false);
@@ -236,21 +272,7 @@ public class MarkController extends BaseController {
         return task;
     }
 
-    /**
-     * 设置任务通用属性
-     * 
-     * @param task
-     * @param marker
-     * @param stage
-     * @param count
-     * @param bmList
-     */
-    private void setTaskParameter(Task task, Marker marker) {
-        task.setMarkId(marker.getId());
-        task.setSpent(new Date().getTime());
-    }
-
-    private Task getTask(Marker marker) {
+    private Task getFormalTask(Marker marker) {
         int retry = 1;
         Task task = null;
         while (retry <= 10 && task == null) {
@@ -262,7 +284,6 @@ public class MarkController extends BaseController {
             for (MarkLibrary library : list) {
                 if (markService.applyLibrary(library, marker)) {
                     task = taskService.build(library);
-                    setTaskParameter(task, marker);
                     break;
                 }
             }
@@ -273,27 +294,35 @@ public class MarkController extends BaseController {
         return task;
     }
 
+    private Task getTrialTask(Marker marker) {
+        int retry = 1;
+        Task task = null;
+        while (task == null) {
+            List<TrialLibrary> list = trialService.findUnMarkedLibrary(marker.getExamId(), marker.getSubjectCode(),
+                    marker.getGroupNumber(), marker.getId(), retry, 10);
+            if (list.isEmpty()) {
+                break;
+            }
+            for (TrialLibrary library : list) {
+                if (markService.applyLibrary(library, marker)) {
+                    task = taskService.build(library, null);
+                    break;
+                }
+            }
+            retry++;
+        }
+        return task;
+    }
+
     @RequestMapping(value = "/savetask", method = RequestMethod.POST)
     @ResponseBody
-    public JSONObject saveTask(HttpServletRequest request, @RequestBody Task task) {
-        Marker marker = RequestUtils.getWebUser(request).getMarker();
+    public JSONObject saveTask(HttpServletRequest request, @RequestBody MarkResult markResult) {
         JSONObject result = new JSONObject();
+        Marker marker = RequestUtils.getWebUser(request).getMarker();
         boolean success = false;
         try {
             lockService.lockMarker(marker.getId());
-            task.setSpent((new Date().getTime() - task.getSpent()) / 1000);
-            MarkLibrary library = libraryService.findById(task.getLibraryId());
-            library.setMarkerId(marker.getId());
-            library.setMarkerTime(new Date());
-            library.setMarkerScore(task.getTotalScore());
-            library.setMarkerScoreList(task.getScoreList());
-            library.setStatus(LibraryStatus.MARKED);
-            library.setTags(StringUtils.trimToNull(task.getTags()));
-            success = markService.submitLibrary(library, marker, task.getTrackList(library),
-                    task.getSpecialTagList(library));
-            if (success) {
-                markService.releaseLibrary(library, marker);
-            }
+            success = markService.submitTask(markResult, marker);
         } catch (Exception e) {
             log.error("save task error", e);
         } finally {
@@ -312,20 +341,35 @@ public class MarkController extends BaseController {
     public Object history(HttpServletRequest request, @RequestParam int pageNumber, @RequestParam int pageSize)
             throws Exception {
         Marker marker = RequestUtils.getWebUser(request).getMarker();
-        // 查找给分任务历史
-        MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
-        query.setExamId(marker.getExamId());
-        query.setSubjectCode(marker.getSubjectCode());
-        query.setMarkerId(marker.getId());
-        query.addStatus(LibraryStatus.MARKED);
-        query.setGroupNumber(marker.getGroupNumber());
-        query.setPageNumber(pageNumber);
-        query.setPageSize(pageSize);
-        query.orderByMarkerTimeDesc();
-        List<Task> list = taskService.findByQuery(query);
-        for (Task task : list) {
-            task.setPrevious(true);
-            setTaskParameter(task, marker);
+        List<Task> list = new ArrayList<>();
+        MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+        if (group != null && group.getStatus() == MarkStatus.FORMAL) {
+            // 正评查找已给分的评卷任务
+            MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
+            query.setExamId(marker.getExamId());
+            query.setSubjectCode(marker.getSubjectCode());
+            query.setMarkerId(marker.getId());
+            query.addStatus(LibraryStatus.MARKED);
+            query.setGroupNumber(marker.getGroupNumber());
+            query.setPageNumber(pageNumber);
+            query.setPageSize(pageSize);
+            query.orderByMarkerTimeDesc();
+            list = taskService.findByQuery(query);
+            for (Task task : list) {
+                task.setPrevious(true);
+            }
+        } else if (group != null && group.getStatus() == MarkStatus.TRIAL) {
+            // 试评查找给分历史记录
+            List<TrialHistory> historyList = trialService.findHistory(marker.getExamId(), marker.getSubjectCode(),
+                    marker.getGroupNumber(), marker.getId(), pageNumber, pageSize);
+            for (TrialHistory history : historyList) {
+                TrialLibrary library = trialService.findLibrary(history.getLibraryId());
+                if (library != null) {
+                    Task task = taskService.build(library, history);
+                    task.setPrevious(true);
+                    list.add(task);
+                }
+            }
         }
         return list;
     }

+ 7 - 0
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupAdd.jsp

@@ -61,6 +61,13 @@
                 <form:input path="scoreList" htmlEscape="false" maxlength="100" class="required"/>
             </div>
         </div>
+        <div class="control-group">
+            <label class="control-label">试评数量</label>
+            <div class="controls">
+                <form:input path="trialCount" htmlEscape="false" min="0" class="digits" type="number" value="0"/>
+                <label>0表示跳过试评</label>
+            </div>
+        </div>
         <div class="control-group">
             <label class="control-label">评卷模式</label>
             <div class="controls">

+ 9 - 1
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupEditSimple.jsp

@@ -66,6 +66,14 @@
 				<form:input path="title" htmlEscape="false" maxlength="30" class="required"/>
 			</div>
 		</div>
+		<c:if test="${group.status==TRIAL}">
+		<div class="control-group">
+            <label class="control-label">试评数量</label>
+            <div class="controls">
+                <form:input path="trialCount" htmlEscape="false" min="1" class="required digits" type="number"/>
+            </div>
+        </div>
+        </c:if>
 		<div class="control-group">
             <label class="control-label">评卷模式</label>
             <div class="controls">
@@ -98,7 +106,7 @@
                 <input type="checkbox" id="openDouble">开启
             </div>
         </div>
-        <div  class="doubleDiv">
+        <div class="doubleDiv">
         <div class="control-group">
             <label class="control-label">双评比例</label>
             <div class="controls">

+ 16 - 8
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupList.jsp

@@ -37,26 +37,25 @@
 	<table id="contentTable" class="table table-striped table-bordered table-condensed">
 		<thead>
 			<tr>
+			    <th>大题号</th>
 				<th>大题名称</th>
-				<th>大题号</th>
 				<th>步骤分</th>
-				<!--<th>裁切小图</th>-->
 				<th>评卷员人数</th>
 				<th>任务总数</th>
 				<th>完成总数</th>
 				<th>剩余总数</th>
 				<th>正在评卷</th>
 				<th>进度</th>
+				<th>状态</th>
 				<th>操作</th>
 			</tr>
 		</thead>
 		<tbody>
 		<c:forEach items="${resultList}" var="result">
 			<tr>
+			    <td>${result.number}</td>
 				<td>${result.title}</td>
-				<td>${result.number}</td>
 				<td>${result.scoreList}</td>
-				<!--<td>${result.picList}</td>-->
                 <td>
                     <a href="${ctx}/admin/exam/marker?subjectCode=${result.subjectCode}&groupNumber=${result.number}">${result.markerCount}</a>
                 </td>
@@ -64,11 +63,16 @@
 				<td>${result.markedCount}</td>
 				<td>${result.leftCount}</td>
 				<td>${result.currentCount}</td>
+				<td>${result.percent}%</td>
+				<td>${result.status.name}</td>
 				<td>
-				  ${result.percent}%
-				</td>
-				<td>
-				    <c:if test="${web_user.schoolAdmin==true}">
+				    <c:if test="${web_user.schoolAdmin==true && result.status!=FINISH}">
+				    <c:if test="${result.status==TRIAL}">
+                    <a href="${ctx}/admin/exam/group/changeStatus?subjectCode=${result.subjectCode}&number=${result.number}&status=FORMAL" data-number="${result.number}" class="edit-button">开始正评</a>
+                    </c:if>
+				    <c:if test="${result.status==FORMAL}">
+                    <a href="${ctx}/admin/exam/group/changeStatus?subjectCode=${result.subjectCode}&number=${result.number}&status=FINISH" data-number="${result.number}" class="edit-button">结束</a>
+                    </c:if>
 					<c:if test="${result.currentCount>0}">
 					<a href="${ctx}/admin/exam/group/release?subjectCode=${result.subjectCode}&number=${result.number}">回收</a>
 					</c:if>
@@ -78,6 +82,10 @@
 					<a href="${ctx}/admin/exam/group/edit-simple?subjectCode=${result.subjectCode}&number=${result.number}" data-number="${result.number}" class="edit-button">修改</a>
 					<a href="${ctx}/admin/exam/group/delete?subjectCode=${result.subjectCode}&number=${result.number}" data-number="${result.number}" class="delete-button">删除</a>
 					</c:if>
+					
+					<c:if test="${web_user.schoolAdmin==true && result.status==FINISH}">
+					<a href="${ctx}/admin/exam/group/changeStatus?subjectCode=${result.subjectCode}&number=${result.number}&status=FORMAL" data-number="${result.number}" class="edit-button">开始正评</a>
+					</c:if>
 				</td>
 			</tr>
 		</c:forEach>

+ 6 - 4
stmms-web/src/main/webapp/static/mark-new/js/mark-control.js

@@ -183,6 +183,11 @@ MarkControl.prototype.initTriggers = function(option) {
         }
         this.trigger('center.width.change');
     });
+    this.on('task.load.finish', this, function(event, context, eventObject) {
+        if(context.task!=undefined){
+        	context.task.spent = new Date().getTime();
+        }
+    });
     this.on('task.get.finish', this, function(event, context, eventObject) {
         context.prefetchCallback = undefined;
     });
@@ -244,10 +249,6 @@ MarkControl.prototype.initTriggers = function(option) {
         context.task = undefined;
         self.getTask();
     });
-    
-    this.on('task.submit.forceSpecialTag', this, function(event, context, eventObject) {
-        
-    });
 
     $(document).keypress(this, function(event) {
         if (self.context.listenKeyboard != false) {
@@ -520,6 +521,7 @@ MarkControl.prototype.submitTask = function(submitUrl) {
         submitObj.markFinish = undefined;
         submitObj.markTime = undefined;
         submitObj.arbitrationList = undefined;
+        submitObj.spent = new Date().getTime() - submitObj.spent;
 
         this.trigger('task.submit.before');
         

+ 74 - 55
stmms-web/src/main/webapp/static/mark-new/js/modules/mark-status.js

@@ -9,18 +9,26 @@ function MarkStatus(option) {
     this.markControl = option.markControl;
     this.init(option);
     this.markControl.on('task.get.before', this, function(event, context, statusInfo) {
-        this.topStatus.find('#stage-name').html('正在加载');
-        this.studentTitle.hide();
-        this.objectiveArea.hide();
+        this.changeStatus('正在加载');
+        this.clearTopTitle();
     });
     this.markControl.on('task.get.success', this, function(event, context, eventObject) {
         var task = context.task;
+        this.changeStatus(task.statusName);
         //修改页面显示
-        this.topStatus.find('#stage-name').html('');
-        this.topStatus.find('#student-number').html(task.studentId);
-        this.studentTitle.show();
-
-        if (task.objectiveScore != undefined) {
+        if(task.studentId && task.studentId!=''){
+        	this.topStatus.find('#student-number').html(task.studentId);
+        	this.studentTitle.show();
+        }else{
+        	this.studentTitle.hide();
+        }
+        if(task.libraryId && task.libraryId!=''){
+        	this.topStatus.find('#library-number').html(task.libraryId);
+        	this.libraryTitle.show();
+        }else{
+        	this.libraryTitle.hide();
+        }
+        if (task.objectiveScore) {
             this.objectiveArea.find('#objective-score').html(task.objectiveScore);
             this.objectiveArea.show();
         } else {
@@ -29,33 +37,30 @@ function MarkStatus(option) {
         this.checkTopCount(task);
     });
     this.markControl.on('task.get.none', this, function(event, context, status) {
-        this.topStatus.find('#stage-name').html('');
-        this.topStatus.find('#student-number').html('');
-        this.studentTitle.hide();
-        this.objectiveArea.hide();
+    	this.changeStatus();
+        this.clearTopTitle();
     });
     this.markControl.on('task.get.finish', this, function(event, context, status) {
-        this.topStatus.find('#stage-name').html('');
-        this.topStatus.find('#student-number').html('');
-        this.studentTitle.hide();
-        this.objectiveArea.hide();
+        this.changeStatus();
+        this.clearTopTitle();
     });
     this.markControl.on('mark.status.change', this, function(event, context, status) {
         this.status = status;
         this.render(status);
     });
     this.markControl.on('view.sidebar.open', this, function(event, context, eventObject) {
-        this.blockProgress.hide();
+        this.progress.hide();
     });
     this.markControl.on('view.sidebar.close', this, function(event, context, eventObject) {
-        this.blockProgress.show();
+        this.progress.show();
     });
 }
 
 MarkStatus.prototype.init = function(option) {
     this.topStatus = getDom(this.status_dom, this.markControl).prependTo(this.markControl.container.header);
-    this.blockProgress = getDom(this.progress_dom, this.markControl).insertAfter(this.topStatus);
+    this.progress = getDom(this.progress_dom, this.markControl).insertAfter(this.topStatus);
     this.subjectTitle = this.topStatus.find('#subject-title');
+    this.libraryTitle = this.topStatus.find('#library-title');
     this.studentTitle = this.topStatus.find('#student-title');
     this.objectiveArea = this.topStatus.find('#objective-area');
 
@@ -85,36 +90,56 @@ MarkStatus.prototype.checkTopCount = function(task) {
     }
 }
 
+MarkStatus.prototype.clearTopTitle = function() {
+	this.topStatus.find('#student-number').html('');
+	this.topStatus.find('#library-number').html('');
+	this.topStatus.find('#objective-score').html('');
+	this.studentTitle.hide();
+	this.libraryTitle.hide();
+	this.objectiveArea.hide();
+}
+
+MarkStatus.prototype.changeStatus = function(name) {
+	if(name!=undefined && name!=''){
+    	this.topStatus.find('#status-name').html(name);
+	}else{
+		this.topStatus.find('#status-name').html('');
+	}
+}
+
+MarkStatus.prototype.changeTopCount = function(topCount) {
+	if(topCount && topCount > 0){
+		this.progress.find('#top-count').html(topCount);
+		this.progress.find('#top-count-area').show();
+	}else{
+       	this.progress.find('#top-count').html('');
+        this.progress.find('#top-count-area').hide();
+    }
+}
+
 MarkStatus.prototype.render = function(status) {
     if (status != undefined && status.valid === true) {
-        var topCount = status.topCount;
-        this.topStatus.find('#mark-count').html(status.personCount);
-
+    	//个人评卷数量
+        this.topStatus.find('#person-count').html(status.personCount);
         //大题进度信息区域
-        if (this.blockProgress != undefined) {
-            var totalCount = status.totalCount;
-            var markedCount = status.markedCount;
-            var exceptionCount = status.exceptionCount;
-            var leftCount = totalCount > (markedCount + exceptionCount) ? (totalCount - markedCount - exceptionCount) : 0;
-            var markedPercent = totalCount > 0 ? new Number((totalCount - leftCount) * 100 / totalCount).toFixed(0) : '0';
-            if (markedPercent == '100' && leftCount > 0) {
-                markedPercent = '99';
-            } else if (markedPercent == '0' && markedCount > 0) {
-                markedPercent = '1';
-            }
-            if(topCount > 0){
-                if(this.blockProgress.find('#top-count').length == 0){
-                  this.blockProgress.find('#todo-count').after('<i>任务数<em id="top-count"></em></i>');
-                  this.blockProgress.find('#top-count').html(topCount);
-                }
-            }
-            this.blockProgress.find('#total-count').html(totalCount);
-            this.blockProgress.find('#marked-count').html(markedCount);
-            this.blockProgress.find('#todo-count').html(leftCount);
-            this.blockProgress.find('#exception-count').html(exceptionCount);
-            this.blockProgress.find('#marked-percent').html(markedPercent + '%');
+        var totalCount = status.totalCount;
+		var markedCount = status.markedCount;
+        var exceptionCount = status.exceptionCount;            
+        var leftCount = totalCount > (markedCount + exceptionCount) ? (totalCount - markedCount - exceptionCount) : 0;
+        var markedPercent = totalCount > 0 ? new Number((totalCount - leftCount) * 100 / totalCount).toFixed(0) : '0';
+        if (markedPercent == '100' && leftCount > 0) {
+            markedPercent = '99';
+        } else if (markedPercent == '0' && markedCount > 0) {
+            markedPercent = '1';
         }
-
+        this.progress.find('#total-count').html(totalCount);
+        this.progress.find('#marked-count').html(markedCount);
+        this.progress.find('#todo-count').html(leftCount);
+        this.progress.find('#exception-count').html(exceptionCount);
+        this.progress.find('#marked-percent').html(markedPercent + '%');
+		//评卷任务上限
+		this.changeTopCount(status.topCount);
+        //判断评卷是否结束
         if (status.totalCount > 0 && status.totalCount == status.markedCount) {
             this.markControl.context.isFinish = true;
         }
@@ -123,25 +148,19 @@ MarkStatus.prototype.render = function(status) {
 
 MarkStatus.prototype.status_dom = '<p class="text">\
 <i id="subject-title" style="cursor:pointer"><em id="subject-name"></em></i>\
-<i id="stage-name"></i>\
+<i id="status-name"></i>\
+<i id="library-title" style="display:none">任务编号<em id="library-number"></em></i>\
 <i id="student-title" style="display:none">考生编号<em id="student-number"></em></i>\
 <i id="objective-area" style="display:none">客观得分<em id="objective-score"></em></i>\
-<i>评卷数<em id="mark-count"></em></i>\
+<i>已评<em id="person-count"></em></i>\
 </p>';
 
 MarkStatus.prototype.progress_dom = '<p class="text">\
+<i id="top-count-area" style="display:none">分配<em id="top-count"></em></i>\
 <i>未评<em id="todo-count"></em></i>\
 <i>进度<em id="marked-percent"></em></i>\
 </p>';
 
-MarkStatus.prototype.block_progress_bak_dom = '<p class="text">\
-<i>总数<em id="total-count"></em></i>\
-<i>已评<em id="marked-count"></em></i>\
-<i>未评<em id="todo-count"></em></i>\
-<i>异常<em id="exception-count"></em></i>\
-<i>进度<em id="marked-percent"></em></i>\
-</p>';
-
 MarkStatus.prototype.popover_dom = '<div class="warning-popover">\
 <p>分配任务已评完,是否继续?</p>\
 <a href="#" class="btn btn-large btn-primary text-c" id="continue-button">继续</a>\