Browse Source

Merge commit '4123e34a372554332aec288f174f2423d60c6515' into stmms_ft_dev

* commit '4123e34a372554332aec288f174f2423d60c6515':
  去掉MarkServiceImpl无用的事物注解
  为MarkService添加全局内存锁,将删除、重置等操作置于互斥锁保护下
  修改ExamStudentDao的findUnLibraryStudent方法SQL,增加breach=false判断条件
  修改ExamStudentDao的findAbsentOrBreachLibraryStudent方法SQL内容,absent和breach判断条件应该改为or关系
ting.yin 6 years ago
parent
commit
5de7c92402

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

@@ -96,17 +96,17 @@ public interface ExamStudentDao
 
 
     public List<ExamStudent> findByExamIdAndSubjectCodeAndUploadTimeNotNull(int examId, String code);
     public List<ExamStudent> findByExamIdAndSubjectCodeAndUploadTimeNotNull(int examId, String code);
 
 
-    @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and s.absent=false and s.uploadTime!=null "
+    @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and s.absent=false and s.breach=false and s.uploadTime!=null "
             + "and not exists (select l.id from MarkLibrary l where l.studentId=s.id and l.groupNumber=?3)")
             + "and not exists (select l.id from MarkLibrary l where l.studentId=s.id and l.groupNumber=?3)")
     public List<ExamStudent> findUnLibraryStudent(Integer examId, String subjectCode, Integer groupNumber,
     public List<ExamStudent> findUnLibraryStudent(Integer examId, String subjectCode, Integer groupNumber,
             Pageable page);
             Pageable page);
 
 
-    @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and s.absent=false and s.uploadTime!=null and s.uploadTime>=?4 "
+    @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and s.absent=false and s.breach=false and s.uploadTime!=null and s.uploadTime>=?4 "
             + "and not exists (select l.id from MarkLibrary l where l.studentId=s.id and l.groupNumber=?3)")
             + "and not exists (select l.id from MarkLibrary l where l.studentId=s.id and l.groupNumber=?3)")
     public List<ExamStudent> findUnLibraryStudent(Integer examId, String subjectCode, Integer groupNumber,
     public List<ExamStudent> findUnLibraryStudent(Integer examId, String subjectCode, Integer groupNumber,
             Date minUploadTime, Pageable page);
             Date minUploadTime, Pageable page);
 
 
-    @Query("select s from ExamStudent s where s.examId=?1 and s.subjectCode=?2 and s.absent=true and s.breach=true and "
+    @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)")
             + "exists (select l.id from MarkLibrary l where l.studentId=s.id)")
     public List<ExamStudent> findAbsentOrBreachLibraryStudent(Integer examId, String subjectCode);
     public List<ExamStudent> findAbsentOrBreachLibraryStudent(Integer examId, String subjectCode);
 
 

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

@@ -29,6 +29,12 @@ import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.biz.utils.CurrentTaskUtil;
 import cn.com.qmth.stmms.biz.utils.CurrentTaskUtil;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
 
 
+/**
+ * 与评卷相关的所有定时任务
+ * 
+ * @author luoshi
+ *
+ */
 @Component
 @Component
 public class MarkCronService {
 public class MarkCronService {
 
 
@@ -115,14 +121,6 @@ public class MarkCronService {
                 ExamStudent student = studentService.findUnLibraryStudent(exam.getId(), subject.getCode(),
                 ExamStudent student = studentService.findUnLibraryStudent(exam.getId(), subject.getCode(),
                         group.getNumber(), lastBuildTime);
                         group.getNumber(), lastBuildTime);
                 while (student != null) {
                 while (student != null) {
-                    // 重复检测大题状态
-                    // TODO - 需要替换成读写锁
-                    // MarkGroup current = group;
-                    // if (current == null || (current.getBuildTime() == null &&
-                    // lastBuildTime != null)) {
-                    // // 大题已被删除,或者已被重置,直接退出循环
-                    // break;
-                    // }
                     // 补充学习中心集合
                     // 补充学习中心集合
                     Campus campus = campusMap.get(student.getSchoolId() + "_" + student.getCampusName());
                     Campus campus = campusMap.get(student.getSchoolId() + "_" + student.getCampusName());
                     if (campus == null) {
                     if (campus == null) {
@@ -135,8 +133,13 @@ public class MarkCronService {
                         }
                         }
                     }
                     }
                     // 尝试构造评卷任务
                     // 尝试构造评卷任务
-                    markService.buildLibrary(student, campus, group);
-                    lastBuildTime = student.getUploadTime();
+                    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(),
                     student = studentService.findUnLibraryStudent(exam.getId(), subject.getCode(), group.getNumber(),
                             lastBuildTime);
                             lastBuildTime);

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

@@ -0,0 +1,111 @@
+package cn.com.qmth.stmms.biz.mark.service.Impl;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+/**
+ * 与评卷相关的全局状态锁<br>
+ * 暂时只实现JVM内存锁,后续加上Redis实现的集群锁
+ * 
+ * @author luoshi
+ *
+ */
+@Component
+public class MarkLockService {
+
+    protected static final Logger log = LoggerFactory.getLogger(MarkLockService.class);
+
+    private Map<String, AtomicBoolean> groupMap = new HashMap<>();
+
+    private Map<Integer, Boolean> studentMap = new ConcurrentHashMap<>();
+
+    /**
+     * 对评卷分组进行重置/删除操作前,先尝试锁定
+     * 
+     * @param group
+     */
+    public void lockGroup(Integer examId, String subjectCode, Integer groupNumber) {
+        AtomicBoolean groupLock = getLock(groupMap, getKey(examId, subjectCode, groupNumber));
+        while (!groupLock.get()) {
+            groupLock.compareAndSet(false, true);
+        }
+    }
+
+    /**
+     * 对评卷分组进行重置/删除操作后,释放锁定
+     * 
+     * @param group
+     */
+    public void unlockGroup(Integer examId, String subjectCode, Integer groupNumber) {
+        AtomicBoolean groupLock = getLock(groupMap, getKey(examId, subjectCode, groupNumber));
+        while (groupLock.get()) {
+            groupLock.compareAndSet(true, false);
+        }
+    }
+
+    /**
+     * 等待评卷分组释放锁定
+     * 
+     * @param group
+     */
+    public void waitUnlockGroup(Integer examId, String subjectCode, Integer groupNumber) {
+        AtomicBoolean groupLock = getLock(groupMap, getKey(examId, subjectCode, groupNumber));
+        while (groupLock.get()) {
+            ;
+        }
+    }
+
+    /**
+     * 对考生评卷任务进行删除操作前,先尝试锁定
+     * 
+     * @param studentId
+     */
+    public void lockStudent(Integer studentId) {
+        studentMap.put(studentId, Boolean.TRUE);
+    }
+
+    /**
+     * 对考生评卷任务进行删除操作后,释放锁定
+     * 
+     * @param studentId
+     */
+    public void unlockStudent(Integer studentId) {
+        studentMap.remove(studentId);
+    }
+
+    /**
+     * 等待考生释放锁定
+     * 
+     * @param studentId
+     */
+    public void waitUnlockStudent(Integer studentId) {
+        while (studentMap.get(studentId) != null) {
+            ;
+        }
+    }
+
+    private AtomicBoolean getLock(Map<String, AtomicBoolean> map, String key) {
+        AtomicBoolean lock = map.get(key);
+        if (lock == null) {
+            synchronized (map) {
+                lock = map.get(key);
+                if (lock != null) {
+                    lock = new AtomicBoolean(false);
+                    map.put(key, lock);
+                }
+            }
+        }
+        return lock;
+    }
+
+    private String getKey(Integer examId, String subjectCode, Integer groupNumber) {
+        return examId + "_" + subjectCode + "_" + groupNumber;
+    }
+
+}

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

@@ -76,6 +76,9 @@ public class MarkServiceImpl implements MarkService {
     @Autowired
     @Autowired
     private MarkSpecialTagDao specialTagDao;
     private MarkSpecialTagDao specialTagDao;
 
 
+    @Autowired
+    private MarkLockService lockService;
+
     @Value("${mark.cleanTimeoutMinute}")
     @Value("${mark.cleanTimeoutMinute}")
     private long timeoutMinute;
     private long timeoutMinute;
 
 
@@ -107,7 +110,6 @@ public class MarkServiceImpl implements MarkService {
      * @param group
      * @param group
      */
      */
     @Override
     @Override
-    @Transactional
     public void releaseByGroup(MarkGroup group) {
     public void releaseByGroup(MarkGroup group) {
         CurrentTaskUtil.clear(group.getExamId(), group.getSubjectCode(), group.getNumber());
         CurrentTaskUtil.clear(group.getExamId(), group.getSubjectCode(), group.getNumber());
     }
     }
@@ -120,15 +122,22 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Override
     @Transactional
     @Transactional
     public void resetByGroup(MarkGroup group) {
     public void resetByGroup(MarkGroup group) {
-        groupDao.resetCount(group.getExamId(), group.getSubjectCode(), group.getNumber());
-        libraryDao.resetByExamIdAndSubjectCodeAndNumber(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());
-        releaseByGroup(group);
+        try {
+            lockService.lockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+            groupDao.resetCount(group.getExamId(), group.getSubjectCode(), group.getNumber());
+            libraryDao.resetByExamIdAndSubjectCodeAndNumber(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());
+            releaseByGroup(group);
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            lockService.unlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        }
     }
     }
 
 
     /**
     /**
@@ -139,18 +148,25 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Override
     @Transactional
     @Transactional
     public void deleteGroup(MarkGroup group) {
     public void deleteGroup(MarkGroup group) {
-        questionDao.deleteByExamIdAndSubjectCodeAndObjectiveAndMainNumber(group.getExamId(), group.getSubjectCode(),
-                false, group.getNumber());
-        libraryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
-                group.getNumber());
-        arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
-                group.getNumber());
-        markerDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
-                group.getNumber());
-        subjectService.updateScore(group.getExamId(), group.getSubjectCode());
-
-        releaseByGroup(group);
-        groupDao.delete(group);
+        try {
+            lockService.lockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+            questionDao.deleteByExamIdAndSubjectCodeAndObjectiveAndMainNumber(group.getExamId(), group.getSubjectCode(),
+                    false, group.getNumber());
+            libraryDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+            arbitrateDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+            markerDao.deleteByExamIdAndSubjectCodeAndGroupNumber(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
+            subjectService.updateScore(group.getExamId(), group.getSubjectCode());
+
+            releaseByGroup(group);
+            groupDao.delete(group);
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            lockService.unlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        }
     }
     }
 
 
     /**
     /**
@@ -191,7 +207,6 @@ public class MarkServiceImpl implements MarkService {
      * @param marker
      * @param marker
      */
      */
     @Override
     @Override
-    @Transactional
     public boolean applyLibrary(MarkLibrary library, Marker marker) {
     public boolean applyLibrary(MarkLibrary library, Marker marker) {
         // 首先判断多评情况下,同一个studentId是否已被该评卷员处理过
         // 首先判断多评情况下,同一个studentId是否已被该评卷员处理过
         if (libraryDao.countByStudentIdAndMarkerId(library.getStudentId(), marker.getId()) > 0) {
         if (libraryDao.countByStudentIdAndMarkerId(library.getStudentId(), marker.getId()) > 0) {
@@ -206,7 +221,6 @@ public class MarkServiceImpl implements MarkService {
      * @param marker
      * @param marker
      */
      */
     @Override
     @Override
-    @Transactional
     public void releaseLibrary(MarkLibrary library, Marker marker) {
     public void releaseLibrary(MarkLibrary library, Marker marker) {
         CurrentTaskUtil.remove(marker, getApplyTaskId(library));
         CurrentTaskUtil.remove(marker, getApplyTaskId(library));
     }
     }
@@ -217,7 +231,6 @@ public class MarkServiceImpl implements MarkService {
      * @param marker
      * @param marker
      */
      */
     @Override
     @Override
-    @Transactional
     public void releaseByMarker(Marker marker) {
     public void releaseByMarker(Marker marker) {
         CurrentTaskUtil.clear(marker);
         CurrentTaskUtil.clear(marker);
     }
     }
@@ -230,12 +243,19 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Override
     @Transactional
     @Transactional
     public void deleteMarker(Marker marker) {
     public void deleteMarker(Marker marker) {
-        releaseByMarker(marker);
-        libraryDao.resetByMarkerId(marker.getId(), LibraryStatus.WAITING);
-        trackDao.deleteByMarkerId(marker.getId());
-        specialTagDao.deleteByMarkerId(marker.getId());
-        updateLibraryCount(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
-        markerDao.delete(marker);
+        try {
+            lockService.lockGroup(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+            releaseByMarker(marker);
+            libraryDao.resetByMarkerId(marker.getId(), LibraryStatus.WAITING);
+            trackDao.deleteByMarkerId(marker.getId());
+            specialTagDao.deleteByMarkerId(marker.getId());
+            updateLibraryCount(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+            markerDao.delete(marker);
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            lockService.unlockGroup(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+        }
     }
     }
 
 
     /**
     /**
@@ -246,11 +266,18 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Override
     @Transactional
     @Transactional
     public void deleteByStudent(ExamStudent student) {
     public void deleteByStudent(ExamStudent student) {
-        trackDao.deleteByStudentId(student.getId());
-        specialTagDao.deleteByStudentId(student.getId());
-        libraryDao.deleteByStudentId(student.getId());
-        arbitrateDao.deleteByStudentId(student.getId());
-        updateLibraryCount(student.getExamId(), student.getSubjectCode());
+        try {
+            lockService.lockStudent(student.getId());
+            trackDao.deleteByStudentId(student.getId());
+            specialTagDao.deleteByStudentId(student.getId());
+            libraryDao.deleteByStudentId(student.getId());
+            arbitrateDao.deleteByStudentId(student.getId());
+            updateLibraryCount(student.getExamId(), student.getSubjectCode());
+        } catch (Exception e) {
+            throw e;
+        } finally {
+            lockService.unlockStudent(student.getId());
+        }
     }
     }
 
 
     /**
     /**
@@ -269,6 +296,14 @@ public class MarkServiceImpl implements MarkService {
         if (group == null) {
         if (group == null) {
             return;
             return;
         }
         }
+        // 等待大题释放锁定
+        lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        // 等待考生释放锁定
+        lockService.waitUnlockStudent(library.getStudentId());
+        // 若该评卷任务已被删除,则直接返回
+        if (!libraryDao.exists(library.getId())) {
+            return;
+        }
         // 保存阅卷轨迹
         // 保存阅卷轨迹
         if (trackMap != null) {
         if (trackMap != null) {
             for (String questionNumber : trackMap.keySet()) {
             for (String questionNumber : trackMap.keySet()) {
@@ -330,6 +365,7 @@ public class MarkServiceImpl implements MarkService {
     @Transactional
     @Transactional
     public void backLibrary(MarkLibrary library) {
     public void backLibrary(MarkLibrary library) {
         if (library.getStatus() == LibraryStatus.MARKED) {
         if (library.getStatus() == LibraryStatus.MARKED) {
+            lockService.waitUnlockGroup(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
             trackDao.deleteByStudentIdAndMarkerId(library.getStudentId(), library.getMarkerId());
             trackDao.deleteByStudentIdAndMarkerId(library.getStudentId(), library.getMarkerId());
             specialTagDao.deleteByLibraryId(library.getId());
             specialTagDao.deleteByLibraryId(library.getId());
             libraryDao.resetById(library.getId(), LibraryStatus.BACKED);
             libraryDao.resetById(library.getId(), LibraryStatus.BACKED);
@@ -357,6 +393,7 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Override
     @Transactional
     @Transactional
     public void processArbitrate(ArbitrateHistory history) {
     public void processArbitrate(ArbitrateHistory history) {
+        lockService.waitUnlockGroup(history.getExamId(), history.getSubjectCode(), history.getGroupNumber());
         arbitrateDao.saveAndFlush(history);
         arbitrateDao.saveAndFlush(history);
         libraryDao.updateHeaderResult(history.getStudentId(), history.getGroupNumber(), history.getUserId(),
         libraryDao.updateHeaderResult(history.getStudentId(), history.getGroupNumber(), history.getUserId(),
                 history.getTotalScore(), history.getScoreList(), history.getUpdateTime(), LibraryStatus.ARBITRATED);
                 history.getTotalScore(), history.getScoreList(), history.getUpdateTime(), LibraryStatus.ARBITRATED);
@@ -495,6 +532,10 @@ public class MarkServiceImpl implements MarkService {
     @Override
     @Override
     @Transactional
     @Transactional
     public void buildLibrary(ExamStudent student, Campus campus, MarkGroup group) {
     public void buildLibrary(ExamStudent student, Campus campus, MarkGroup group) {
+        // 等待大题释放锁定
+        lockService.waitUnlockGroup(group.getExamId(), group.getSubjectCode(), group.getNumber());
+        // 等待考生释放锁定
+        lockService.waitUnlockStudent(student.getId());
         // 查询是否已创建评卷任务
         // 查询是否已创建评卷任务
         if (libraryDao.countByStudentIdAndGroupNumber(student.getId(), group.getNumber()) == 0) {
         if (libraryDao.countByStudentIdAndGroupNumber(student.getId(), group.getNumber()) == 0) {
             MarkLibrary library = new MarkLibrary();
             MarkLibrary library = new MarkLibrary();