Răsfoiți Sursa

3.3.0 评卷员管理-加锁

xiaofei 1 an în urmă
părinte
comite
3107c672e6
15 a modificat fișierele cu 934 adăugiri și 14 ștergeri
  1. 9 2
      distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkUserGroupController.java
  2. 19 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/enums/LockType.java
  3. 20 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/LockProvider.java
  4. 107 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/LockService.java
  5. 99 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/impl/CustomLockProvider.java
  6. 134 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/impl/ReadWriteLock.java
  7. 10 1
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkService.java
  8. 13 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkSyncService.java
  9. 4 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkUserGroupService.java
  10. 16 6
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkGroupServiceImpl.java
  11. 93 1
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkServiceImpl.java
  12. 45 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkSyncServiceImpl.java
  13. 44 4
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserGroupServiceImpl.java
  14. 246 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/utils/TaskLock.java
  15. 75 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/utils/TaskLockUtil.java

+ 9 - 2
distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkUserGroupController.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.qmth.boot.api.constant.ApiConstant;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkUserGroupProgressDto;
 import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
 import com.qmth.teachcloud.common.util.Result;
 import com.qmth.teachcloud.common.util.ResultUtil;
 import com.qmth.teachcloud.mark.service.MarkUserGroupService;
@@ -71,7 +72,12 @@ public class MarkUserGroupController {
     @ApiOperation(value = "重置评卷员")
     @RequestMapping(value = "/reset", method = RequestMethod.POST)
     public Result resetMarker(@ApiParam(value = "考试ID", required = true) @RequestParam Long markUserGroupId) {
-        return ResultUtil.ok();
+        boolean resetMarker = markUserGroupService.resetMarker(markUserGroupId);
+        if (resetMarker) {
+            return ResultUtil.ok(true);
+        } else {
+            return ResultUtil.error(ExceptionResultEnum.ERROR.getCode(), "评卷员正在重置");
+        }
     }
 
     /**
@@ -90,7 +96,8 @@ public class MarkUserGroupController {
     @ApiOperation(value = "回收正在评卷的试卷")
     @RequestMapping(value = "/release", method = RequestMethod.POST)
     public Result releaseMarker(@ApiParam(value = "评卷员ID集合", required = true) @RequestParam List<Long> markUserGroupIds) {
-        return ResultUtil.ok();
+        markUserGroupService.releaseMarker(markUserGroupIds);
+        return ResultUtil.ok(true);
     }
 
     /**

+ 19 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/enums/LockType.java

@@ -0,0 +1,19 @@
+package com.qmth.teachcloud.mark.enums;
+
+public enum LockType {
+    EXAM_SUBJECT("exam_subject"), GROUP("group"), GROUP_DELETE("group_delete"), STUDENT("student"), MARK_USER_GROUP("mark_user_group"), MARKER_RESET(
+            "marker_reset"), USER("user"), FORMAL_LIBRARY("formal_library"), TRIAL_LIBRARY("trial_library"), ARBITRATE(
+            "arbitrate"), BATCH_QUALITY("batch_quality"), SCORE_CALCULATE("score_calculate"), GROUP_LIBRARY(
+            "group_library"), DATA_SYNC("data_sync"), SCHOOL("school"), QUESTION_DELETE("question_delete");
+
+    private String name;
+
+    private LockType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+}

+ 20 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/LockProvider.java

@@ -0,0 +1,20 @@
+package com.qmth.teachcloud.mark.lock;
+
+import com.qmth.teachcloud.mark.enums.LockType;
+
+public interface LockProvider {
+
+    void waitLock(LockType type, String key);
+
+    boolean tryLock(LockType type, String key);
+
+    void unlock(LockType type, String key);
+
+    boolean isLocked(LockType type, String key);
+
+    void watch(LockType type, String key);
+
+    void unwatch(LockType type, String key);
+
+    void clear();
+}

+ 107 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/LockService.java

@@ -0,0 +1,107 @@
+package com.qmth.teachcloud.mark.lock;
+
+import com.qmth.teachcloud.mark.enums.LockType;
+import com.qmth.teachcloud.mark.lock.impl.CustomLockProvider;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component("lockService")
+public class LockService implements InitializingBean, ApplicationContextAware {
+
+    protected static final Logger log = LoggerFactory.getLogger(LockService.class);
+
+    private static final String KEY_JOINER = "\t";
+
+    private ApplicationContext context;
+
+    private LockProvider provider;
+
+    /**
+     * 尝试获取排他锁,立即返回获取结果
+     *
+     * @param type
+     * @param keys
+     * @return
+     */
+    public boolean trylock(LockType type, Object... keys) {
+        return provider.tryLock(type, getKeys(keys));
+    }
+
+    /**
+     * 等待获取排他锁,线程阻塞直到成功获取
+     *
+     * @param type
+     * @param keys
+     */
+    public void waitlock(LockType type, Object... keys) {
+        provider.waitLock(type, getKeys(keys));
+    }
+
+    /**
+     * 释放已获取的排他锁
+     *
+     * @param type
+     * @param keys
+     */
+    public void unlock(LockType type, Object... keys) {
+        provider.unlock(type, getKeys(keys));
+    }
+
+    /**
+     * 检测排他锁是否已被获取
+     *
+     * @param type
+     * @param keys
+     * @return
+     */
+    public boolean isLocked(LockType type, Object... keys) {
+        return provider.isLocked(type, getKeys(keys));
+    }
+
+    /**
+     * 等待获取可重入的读锁
+     *
+     * @param type
+     * @param keys
+     */
+    public void watch(LockType type, Object... keys) {
+        provider.watch(type, getKeys(keys));
+    }
+
+    /**
+     * 释放已获取的读锁
+     *
+     * @param type
+     * @param keys
+     */
+    public void unwatch(LockType type, Object... keys) {
+        provider.unwatch(type, getKeys(keys));
+    }
+
+    /**
+     * 释放长时间未使用的锁
+     */
+    public void clear() {
+        provider.clear();
+    }
+
+    private String getKeys(Object... keys) {
+        return StringUtils.join(keys, KEY_JOINER);
+    }
+
+    @Override
+    public void afterPropertiesSet() throws Exception {
+        this.provider = this.context.getBean(CustomLockProvider.class);
+    }
+
+    @Override
+    public void setApplicationContext(ApplicationContext context) throws BeansException {
+        this.context = context;
+    }
+}

+ 99 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/impl/CustomLockProvider.java

@@ -0,0 +1,99 @@
+package com.qmth.teachcloud.mark.lock.impl;
+
+import com.qmth.teachcloud.mark.enums.LockType;
+import com.qmth.teachcloud.mark.lock.LockProvider;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * 自己实现的内存锁控制
+ *
+ * @author luoshi
+ */
+@Component("customLockProvider")
+public class CustomLockProvider implements LockProvider {
+
+    private Map<LockType, Map<String, ReadWriteLock>> lockMap = new HashMap<>();
+
+    @Override
+    public void waitLock(LockType type, String key) {
+        getLock(type, key).write();
+    }
+
+    @Override
+    public boolean tryLock(LockType type, String key) {
+        return getLock(type, key).tryWrite();
+    }
+
+    @Override
+    public boolean isLocked(LockType type, String key) {
+        return getLock(type, key).writing();
+    }
+
+    @Override
+    public void unlock(LockType type, String key) {
+        getLock(type, key).unWrite();
+    }
+
+    @Override
+    public void watch(LockType type, String key) {
+        getLock(type, key).read();
+    }
+
+    @Override
+    public void unwatch(LockType type, String key) {
+        getLock(type, key).unRead();
+    }
+
+    private ReadWriteLock getLock(LockType type, String key) {
+        Map<String, ReadWriteLock> map = lockMap.get(type);
+        if (map == null) {
+            synchronized (lockMap) {
+                map = lockMap.get(type);
+                if (map == null) {
+                    map = new HashMap<>();
+                    lockMap.put(type, map);
+                }
+            }
+        }
+
+        ReadWriteLock lock = map.get(key);
+        if (lock == null) {
+            synchronized (map) {
+                lock = map.get(key);
+                if (lock == null) {
+                    lock = new ReadWriteLock();
+                    map.put(key, lock);
+                }
+            }
+        }
+
+        return lock;
+    }
+
+    public void clear() {
+        if (lockMap.isEmpty()) {
+            return;
+        }
+        synchronized (lockMap) {
+            for (Map<String, ReadWriteLock> map : lockMap.values()) {
+                Set<String> keys = new HashSet<>();
+                for (Entry<String, ReadWriteLock> entry : map.entrySet()) {
+                    if (!entry.getValue().writing() && entry.getValue().readCount() == 0) {
+                        keys.add(entry.getKey());
+                    }
+                }
+                for (String key : keys) {
+                    map.remove(key);
+                }
+                keys.clear();
+            }
+        }
+    }
+
+}

+ 134 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/lock/impl/ReadWriteLock.java

@@ -0,0 +1,134 @@
+package com.qmth.teachcloud.mark.lock.impl;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntUnaryOperator;
+
+/**
+ * 简单读写锁实现<br>
+ * 0表示空闲状态,-1表示正在写,>0表示正在读
+ *
+ * @author luoshi
+ */
+public class ReadWriteLock {
+
+    private static ReadOperator readOperator = new ReadOperator();
+
+    private static UnReadOperator unreadOperator = new UnReadOperator();
+
+    private AtomicInteger value;
+
+    public ReadWriteLock() {
+        this.value = new AtomicInteger(0);
+    }
+
+    public int read() {
+        int result = 0;
+        while ((result = value.updateAndGet(readOperator)) < 1) {
+            ;
+        }
+        return result;
+    }
+
+    public int unRead() {
+        if (value.get() < 1) {
+            throw new RuntimeException("unread error");
+        }
+        return value.updateAndGet(unreadOperator);
+    }
+
+    public int readCount() {
+        return Math.max(0, value.get());
+    }
+
+    public int write() {
+        while (!value.compareAndSet(0, -1)) {
+            ;
+        }
+        return -1;
+    }
+
+    public boolean tryWrite() {
+        return value.compareAndSet(0, -1);
+    }
+
+    public int unWrite() {
+        while (!value.compareAndSet(-1, 0)) {
+            ;
+        }
+        return 0;
+    }
+
+    public boolean writing() {
+        return value.get() == -1;
+    }
+
+    static class ReadOperator implements IntUnaryOperator {
+
+        @Override
+        public int applyAsInt(int operand) {
+            return operand >= 0 ? operand + 1 : operand;
+        }
+
+    }
+
+    static class UnReadOperator implements IntUnaryOperator {
+
+        @Override
+        public int applyAsInt(int operand) {
+            return operand >= 1 ? operand - 1 : operand;
+        }
+
+    }
+
+    static class LockTestThread implements Runnable {
+
+        private ReadWriteLock lock;
+
+        private int number;
+
+        public LockTestThread(int number, ReadWriteLock lock) {
+            this.lock = lock;
+            this.number = number;
+        }
+
+        @Override
+        public void run() {
+            for (int i = 0; i < 10000; i++) {
+                if (Math.random() < 0.7) {
+                    System.out.println(
+                            "[thread-" + number + "]   read: " + lock.read() + "[" + System.currentTimeMillis() + "]");
+                    try {
+                        Thread.sleep(100 * ((i % 10) + 1));
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    System.out.println("[thread-" + number + "] unread: " + lock.unRead() + "["
+                            + System.currentTimeMillis() + "]");
+                } else {
+                    System.out.println(
+                            "[thread-" + number + "]  write:" + lock.write() + "[" + System.currentTimeMillis() + "]");
+                    try {
+                        Thread.sleep(100 * ((i % 10) + 1));
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                    System.out.println("[thread-" + number + "]unwrite: " + lock.unWrite() + "["
+                            + System.currentTimeMillis() + "]");
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        ExecutorService executor = Executors.newFixedThreadPool(5);
+        final ReadWriteLock lock = new ReadWriteLock();
+        for (int i = 0; i < 5; i++) {
+            executor.submit(new LockTestThread(i + 1, lock));
+        }
+
+        executor.shutdown();
+    }
+
+}

+ 10 - 1
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkService.java

@@ -1,10 +1,19 @@
 package com.qmth.teachcloud.mark.service;
 
+import com.qmth.teachcloud.mark.entity.MarkGroup;
+import com.qmth.teachcloud.mark.entity.MarkUserGroup;
+
 /**
  * <p>
  * 评卷相关 服务类
  */
 public interface MarkService {
 
-    Integer applyCurrentCount();
+    int applyCurrentCount(MarkGroup markGroup);
+
+    void releaseByMarkUserGroup(MarkUserGroup markUserGroup);
+
+    int applyCurrentCount(MarkGroup markGroup, Long markUserGroupId);
+
+    void resetMarker(MarkUserGroup markUserGroup);
 }

+ 13 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkSyncService.java

@@ -0,0 +1,13 @@
+package com.qmth.teachcloud.mark.service;
+
+import com.qmth.teachcloud.mark.entity.MarkUserGroup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public interface MarkSyncService {
+
+    public static Logger log = LoggerFactory.getLogger(MarkSyncService.class);
+
+    void markerResetSync(MarkUserGroup markUserGroup);
+
+}

+ 4 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkUserGroupService.java

@@ -32,4 +32,8 @@ public interface MarkUserGroupService extends IService<MarkUserGroup> {
     MarkUserGroup getByExamIdAndPaperNumberAndGroupNumberAndUserId(Long examId, String paperNumber, Integer groupNumber, Long userId);
 
     void deleteMarker(Long markUserGroupId);
+
+    void releaseMarker(List<Long> markUserGroupIds);
+
+    boolean resetMarker(Long markUserGroupId);
 }

+ 16 - 6
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkGroupServiceImpl.java

@@ -21,6 +21,7 @@ import com.qmth.teachcloud.common.service.MarkPaperService;
 import com.qmth.teachcloud.common.service.MarkQuestionService;
 import com.qmth.teachcloud.mark.entity.MarkGroup;
 import com.qmth.teachcloud.mark.entity.MarkTask;
+import com.qmth.teachcloud.mark.entity.MarkUserGroup;
 import com.qmth.teachcloud.mark.mapper.MarkGroupMapper;
 import com.qmth.teachcloud.mark.service.*;
 import org.apache.commons.collections4.CollectionUtils;
@@ -46,16 +47,14 @@ public class MarkGroupServiceImpl extends ServiceImpl<MarkGroupMapper, MarkGroup
 
     @Resource
     private MarkQuestionService markQuestionService;
-
+    @Resource
+    private MarkGroupService markGroupService;
     @Resource
     private MarkPaperService markPaperService;
-
     @Resource
     private MarkUserGroupService markUserGroupService;
-
     @Resource
     private MarkUserClassService markUserClassService;
-
     @Resource
     private MarkTaskService markTaskService;
     @Resource
@@ -184,7 +183,7 @@ public class MarkGroupServiceImpl extends ServiceImpl<MarkGroupMapper, MarkGroup
             markGroupProgressDto.setTaskCount(groupNumberMarkTask.size());
             markGroupProgressDto.setMarkedCount(markTaskService.markedCount(groupNumberMarkTask));
             markGroupProgressDto.setLeftCount(markGroupProgressDto.getTaskCount() - markGroupProgressDto.getMarkedCount());
-            markGroupProgressDto.setCurrentCount(markService.applyCurrentCount());
+            markGroupProgressDto.setCurrentCount(markService.applyCurrentCount(markGroup));
             markGroupProgressDto.setPercent(markTaskService.calcPercent(markGroupProgressDto.getMarkedCount(), markGroupProgressDto.getTaskCount()));
             markGroupProgressDto.setArbitrateCount(markTaskService.waitArbitrateCount(groupNumberMarkTask));
             markGroupProgressDtoList.add(markGroupProgressDto);
@@ -202,16 +201,27 @@ public class MarkGroupServiceImpl extends ServiceImpl<MarkGroupMapper, MarkGroup
             for (MarkGroupClassProgressDto markGroupClassProgressDto : markGroupClassProgressDtoIPage.getRecords()) {
                 List<MarkTask> totalMarkTaskList = new ArrayList<>();
                 List<MarkUser> totalMarkUserList = new ArrayList<>();
+                int count = 0;
                 for (String s : markGroupClassProgressDto.getGroupNumbers().split(",")) {
                     Integer groupNumber = Integer.parseInt(s);
                     totalMarkTaskList.addAll(markTaskService.listByExamIdAndPaperNumberAndGroupNumberAndUserId(examId, paperNumber, groupNumber, null));
                     totalMarkUserList.addAll(markUserGroupService.listGroupUserByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber));
+
+                    MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
+                    List<MarkUser> markUserList = markUserClassService.listClassMarkerByExamIdAndPaperNumberAndGroupNumberAndClassName(examId, paperNumber, groupNumber, markGroupClassProgressDto.getClassName());
+                    for (MarkUser markUser : markUserList) {
+                        MarkUserGroup markUserGroup = markUserGroupService.getByExamIdAndPaperNumberAndGroupNumberAndUserId(examId, paperNumber, groupNumber, markUser.getUserId());
+                        if (markUserGroup != null) {
+                            int markerCount = markService.applyCurrentCount(markGroup);
+                            count += markerCount;
+                        }
+                    }
                 }
                 markGroupClassProgressDto.setMarkerCount(totalMarkUserList.size());
                 markGroupClassProgressDto.setTaskCount(totalMarkTaskList.size());
                 markGroupClassProgressDto.setMarkedCount(markTaskService.markedCount(totalMarkTaskList));
                 markGroupClassProgressDto.setLeftCount(markGroupClassProgressDto.getTaskCount() - markGroupClassProgressDto.getMarkedCount());
-                markGroupClassProgressDto.setCurrentCount(markService.applyCurrentCount());
+                markGroupClassProgressDto.setCurrentCount(count);
                 markGroupClassProgressDto.setPercent(markTaskService.calcPercent(markGroupClassProgressDto.getMarkedCount(), markGroupClassProgressDto.getTaskCount()));
                 markGroupClassProgressDto.setArbitrateCount(markTaskService.waitArbitrateCount(totalMarkTaskList));
             }

+ 93 - 1
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkServiceImpl.java

@@ -1,19 +1,111 @@
 package com.qmth.teachcloud.mark.service.impl;
 
+import com.qmth.teachcloud.common.entity.MarkPaper;
+import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
+import com.qmth.teachcloud.common.service.MarkPaperService;
+import com.qmth.teachcloud.mark.entity.MarkGroup;
+import com.qmth.teachcloud.mark.entity.MarkUserGroup;
+import com.qmth.teachcloud.mark.service.MarkGroupService;
 import com.qmth.teachcloud.mark.service.MarkService;
+import com.qmth.teachcloud.mark.service.MarkUserGroupService;
+import com.qmth.teachcloud.mark.utils.TaskLock;
+import com.qmth.teachcloud.mark.utils.TaskLockUtil;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
+import java.util.List;
+
 @Service
 public class MarkServiceImpl implements MarkService {
 
+    @Resource
+    private MarkPaperService markPaperService;
+    @Resource
+    private MarkGroupService markGroupService;
+    @Resource
+    private MarkUserGroupService markUserGroupService;
+
     /**
      * 某个评卷分组已申请的评卷任务数量
      *
      * @return
      */
     @Override
-    public Integer applyCurrentCount() {
+    public int applyCurrentCount(MarkGroup markGroup) {
         // todo 正在评卷数量查询 (评卷员和评卷分组分开2个方法)
+        TaskLock taskLock = getTaskLock(markGroup);
+        int count = 0;
+        if (taskLock != null) {
+            count = taskLock.count();
+        }
+        return count;
+    }
+
+    private TaskLock getTaskLock(MarkGroup markGroup) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markGroup.getExamId(), markGroup.getPaperNumber());
+        if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
+            return TaskLockUtil.getFormalTask(getGroupKey(markGroup));
+        }
         return null;
     }
+
+    /**
+     * 释放某个评卷员的所有锁定任务
+     *
+     * @param markUserGroup
+     */
+    @Override
+    public void releaseByMarkUserGroup(MarkUserGroup markUserGroup) {
+        TaskLock taskLock = TaskLockUtil.getFormalTask(getGroupKey(markUserGroup));
+        taskLock.clear(markUserGroup.getId());
+        TaskLock lock = TaskLockUtil.getTrialTask(getGroupKey(markUserGroup));
+        lock.clear(markUserGroup.getId());
+    }
+
+    @Override
+    public int applyCurrentCount(MarkGroup markGroup, Long markUserGroupId) {
+        int count = 0;
+        TaskLock taskLock = getTaskLock(markGroup);
+        if (taskLock != null) {
+            count = taskLock.count(markUserGroupId);
+        }
+        return count;
+    }
+
+    @Override
+    public void resetMarker(MarkUserGroup markUserGroup) {
+        MarkPaper markPaper = markPaperService.getByExamIdAndPaperNumber(markUserGroup.getExamId(), markUserGroup.getPaperNumber());
+        if (markPaper == null || markUserGroupService.getById(markUserGroup.getId()) == null) {
+            return;
+        }
+        /*if (markPaper.getStatus() == MarkPaperStatus.FORMAL) {
+            // 遍历相关评卷任务的模式
+            List<MarkLibrary> list = libraryDao.findByMarkerIdAndStatusNotIn(marker.getId(), LibraryStatus.ARBITRATED,
+                    LibraryStatus.WAIT_ARBITRATE, LibraryStatus.PROBLEM);
+            for (MarkLibrary library : list) {
+                trackDao.deleteByLibraryId(library.getId());
+                specialTagDao.deleteByLibraryId(library.getId());
+                libraryDao.resetById(library.getId(), null, null, null, null, LibraryStatus.WAITING,
+                        library.getStatus());
+                lockService.waitlock(LockType.STUDENT, library.getStudentId());
+                updateStudentGroupStatus(library.getStudentId(), library.getExamId(), library.getSubjectCode(),
+                        library.getGroupNumber(), SubjectiveStatus.UNMARK);
+                studentService.updateSubjectiveStatusAndScore(library.getStudentId(), SubjectiveStatus.UNMARK, 0, null);
+                inspectedService.cancelByStudent(library.getStudentId());
+                lockService.unlock(LockType.STUDENT, library.getStudentId());
+            }
+            markerDao.resetById(marker.getId());
+        }
+        updateMarkedCount(group);
+        releaseByMarkUserGroup(markUserGroup);
+        inspectHistoryService.deleteByExamIdAndSubjectCode(marker.getExamId(), marker.getSubjectCode());*/
+    }
+
+    private String getGroupKey(MarkGroup markGroup) {
+        return markGroup.getExamId() + "_" + markGroup.getPaperNumber() + "_" + markGroup.getNumber();
+    }
+
+    private String getGroupKey(MarkUserGroup markUserGroup) {
+        return markUserGroup.getExamId() + "_" + markUserGroup.getPaperNumber() + "_" + markUserGroup.getGroupNumber();
+    }
 }

+ 45 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkSyncServiceImpl.java

@@ -0,0 +1,45 @@
+package com.qmth.teachcloud.mark.service.impl;
+
+import com.qmth.teachcloud.mark.entity.MarkUserGroup;
+import com.qmth.teachcloud.mark.enums.LockType;
+import com.qmth.teachcloud.mark.lock.LockService;
+import com.qmth.teachcloud.mark.service.MarkService;
+import com.qmth.teachcloud.mark.service.MarkSyncService;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+@Service
+public class MarkSyncServiceImpl implements MarkSyncService {
+
+    @Resource
+    private MarkService markService;
+    @Resource
+    private LockService lockService;
+
+    /**
+     * 评卷员重置
+     *
+     * @param markUserGroup
+     */
+    @Async
+    @Override
+    public void markerResetSync(MarkUserGroup markUserGroup) {
+        try {
+            if (markUserGroup != null) {
+                lockService.waitlock(LockType.EXAM_SUBJECT, markUserGroup.getExamId(), markUserGroup.getPaperNumber());
+                lockService.waitlock(LockType.MARK_USER_GROUP, markUserGroup.getId());
+                lockService.watch(LockType.GROUP, markUserGroup.getExamId(), markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber());
+                markService.resetMarker(markUserGroup);
+            }
+        } catch (Exception e) {
+            log.error("reset marker error", e);
+        } finally {
+            lockService.unwatch(LockType.GROUP, markUserGroup.getExamId(), markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber());
+            lockService.unlock(LockType.MARK_USER_GROUP, markUserGroup.getId());
+            lockService.unlock(LockType.EXAM_SUBJECT, markUserGroup.getExamId(), markUserGroup.getPaperNumber());
+            lockService.unlock(LockType.MARKER_RESET, markUserGroup.getId());
+        }
+    }
+}

+ 44 - 4
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserGroupServiceImpl.java

@@ -13,13 +13,15 @@ import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
 import com.qmth.teachcloud.common.service.MarkPaperService;
 import com.qmth.teachcloud.common.service.MarkQuestionService;
 import com.qmth.teachcloud.common.util.ServletUtil;
+import com.qmth.teachcloud.mark.entity.MarkGroup;
 import com.qmth.teachcloud.mark.entity.MarkTask;
 import com.qmth.teachcloud.mark.entity.MarkUserGroup;
+import com.qmth.teachcloud.mark.enums.LockType;
+import com.qmth.teachcloud.mark.lock.LockService;
 import com.qmth.teachcloud.mark.mapper.MarkUserGroupMapper;
-import com.qmth.teachcloud.mark.service.MarkService;
-import com.qmth.teachcloud.mark.service.MarkTaskService;
-import com.qmth.teachcloud.mark.service.MarkUserGroupService;
+import com.qmth.teachcloud.mark.service.*;
 import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
@@ -43,11 +45,17 @@ public class MarkUserGroupServiceImpl extends ServiceImpl<MarkUserGroupMapper, M
     @Resource
     private MarkQuestionService markQuestionService;
     @Resource
+    private MarkGroupService markGroupService;
+    @Resource
     private MarkTaskService markTaskService;
     @Resource
     private MarkPaperService markPaperService;
     @Resource
+    private MarkSyncService markSyncService;
+    @Resource
     private MarkService markService;
+    @Resource
+    private LockService lockService;
 
     @Override
     public IPage<MarkEntranceDto> listEntranceGroup(Long examId, String courseCode, String paperNumber, Integer pageNumber, Integer pageSize) {
@@ -86,7 +94,8 @@ public class MarkUserGroupServiceImpl extends ServiceImpl<MarkUserGroupMapper, M
             List<MarkTask> markTaskList = markTaskService.listByExamIdAndPaperNumberAndGroupNumberAndUserId(examId, paperNumber, record.getGroupNumber(), record.getUserId());
             record.setTaskCount(markTaskList.size());
             record.setMarkedCount(markTaskService.markedCount(markTaskList));
-            record.setCurrentCount(markService.applyCurrentCount());
+            MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, record.getGroupNumber());
+            record.setCurrentCount(markService.applyCurrentCount(markGroup, record.getMarkUserGroupId()));
         }
         return markUserGroupProgressDtoIPage;
     }
@@ -140,4 +149,35 @@ public class MarkUserGroupServiceImpl extends ServiceImpl<MarkUserGroupMapper, M
             this.removeById(markUserGroupId);
         }
     }
+
+    @Override
+    public void releaseMarker(List<Long> markUserGroupIds) {
+        for (Long markUserGroupId : markUserGroupIds) {
+            MarkUserGroup markUserGroup = this.getById(markUserGroupId);
+            if (markUserGroup != null) {
+                try {
+                    lockService.waitlock(LockType.MARK_USER_GROUP, markUserGroupId);
+                    markService.releaseByMarkUserGroup(markUserGroup);
+                } catch (Exception e) {
+                    log.error("release marker error", e);
+                } finally {
+                    lockService.unlock(LockType.MARK_USER_GROUP, markUserGroupId);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean resetMarker(Long markUserGroupId) {
+        MarkUserGroup markUserGroup = this.getById(markUserGroupId);
+        if (markUserGroup != null) {
+            if (lockService.trylock(LockType.MARKER_RESET, markUserGroupId)) {
+                markSyncService.markerResetSync(markUserGroup);
+                return true;
+            } else {
+                return false;
+            }
+        }
+        return true;
+    }
 }

+ 246 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/utils/TaskLock.java

@@ -0,0 +1,246 @@
+package com.qmth.teachcloud.mark.utils;
+
+import com.qmth.teachcloud.common.util.DateDisposeUtils;
+
+import java.util.*;
+
+/**
+ * 链表模式实现的任务互斥锁工具
+ */
+public class TaskLock {
+
+    // 固定头节点
+    private LockNode head;
+
+    // 总量计数
+    private int count;
+
+    public TaskLock() {
+        head = new LockNode(0, 0, 0);
+        count = 0;
+    }
+
+    public synchronized boolean add(Object id, int number, Object owner) {
+        if (head.next == null) {
+            head.append(id, number, owner);
+            count++;
+            return true;
+        } else {
+            LockNode node = head.next;
+            while (true) {
+                // id+number,只能被一个owner获取
+                if (node.isId(id) && node.isNumber(number)) {
+                    return false;
+                }
+                // id只能被一个owner领取一个number
+                else if (node.isId(id) && node.isOwner(owner)) {
+                    return false;
+                }
+                // 跳到下一个node
+                else if (node.hasNext()) {
+                    node = node.next;
+                }
+                // 可以领取
+                else {
+                    node.append(id, number, owner);
+                    count++;
+                    return true;
+                }
+            }
+        }
+    }
+
+    public synchronized boolean exist(Object id, int number, Object owner) {
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isId(id) && node.isNumber(number)) {
+                return node.isOwner(owner);
+            } else {
+                node = node.next;
+            }
+        }
+        return false;
+    }
+
+    public synchronized boolean remove(Object id, int number, Object owner) {
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isId(id) && node.isNumber(number)) {
+                if (node.isOwner(owner)) {
+                    node.remove();
+                    count--;
+                    return true;
+                } else {
+                    return false;
+                }
+            } else {
+                node = node.next;
+            }
+        }
+        return false;
+    }
+
+    public synchronized boolean remove(Object id, int number) {
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isId(id) && node.isNumber(number)) {
+                node.remove();
+                count--;
+                return true;
+            } else {
+                node = node.next;
+            }
+        }
+        return false;
+    }
+
+    public synchronized void clear() {
+        head.next = null;
+        count = 0;
+    }
+
+    public synchronized void clear(Object owner) {
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isOwner(owner)) {
+                node.remove();
+                count--;
+            }
+            node = node.next;
+        }
+    }
+
+    public synchronized void expire(long expireTime) {
+        long current = System.currentTimeMillis();
+        LockNode node = head.next;
+        while (node != null) {
+            if ((current - node.time) > expireTime) {
+                node.remove();
+                count--;
+            }
+            node = node.next;
+        }
+    }
+
+    public synchronized boolean refresh(Object id, int number, Object owner) {
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isId(id) && node.isNumber(number) && node.isOwner(owner)) {
+                node.time = System.currentTimeMillis();
+                return true;
+            }
+            node = node.next;
+        }
+        return false;
+    }
+
+    public synchronized void refresh(Object owner) {
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isOwner(owner)) {
+                node.time = System.currentTimeMillis();
+            }
+            node = node.next;
+        }
+    }
+
+    public int count() {
+        return count;
+    }
+
+    public int count(Object owner) {
+        int count = 0;
+        LockNode node = head.next;
+        while (node != null) {
+            if (node.isOwner(owner)) {
+                count++;
+            }
+            node = node.next;
+        }
+        return count;
+    }
+
+    public List<Map<String, Object>> list() {
+        List<Map<String, Object>> list = new LinkedList<>();
+        LockNode node = head.next;
+        while (node != null) {
+            Map<String, Object> map = new HashMap<>();
+            map.put("taskId", node.getId());
+            map.put("number", node.getNumber());
+            map.put("markerId", node.getOwner());
+            map.put("time", DateDisposeUtils.parseDateToStr(DateDisposeUtils.YYYY_MM_DD_HH_MM_SS, new Date(node.getTime())));
+            list.add(map);
+            node = node.next;
+        }
+        return list;
+    }
+
+    private static class LockNode {
+
+        private LockNode previous;
+
+        private LockNode next;
+
+        private Object id;
+
+        private int number;
+
+        private Object owner;
+
+        private long time;
+
+        LockNode(Object id, int number, Object owner) {
+            this.id = id;
+            this.number = number;
+            this.owner = owner;
+            this.time = System.currentTimeMillis();
+        }
+
+        private boolean hasNext() {
+            return this.next != null;
+        }
+
+        private boolean isId(Object id) {
+            return this.id.equals(id);
+        }
+
+        private boolean isNumber(int number) {
+            return this.number == number;
+        }
+
+        private boolean isOwner(Object owner) {
+            return this.owner.equals(owner);
+        }
+
+        private void remove() {
+            this.previous.next = this.next;
+            if (this.next != null) {
+                this.next.previous = this.previous;
+            }
+        }
+
+        private void append(Object id, int number, Object owner) {
+            LockNode node = new LockNode(id, number, owner);
+            this.next = node;
+            node.previous = this;
+        }
+
+        public Object getId() {
+            return id;
+        }
+
+        public int getNumber() {
+            return number;
+        }
+
+        public Object getOwner() {
+            return owner;
+        }
+
+        public long getTime() {
+            return time;
+        }
+
+    }
+
+}

+ 75 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/utils/TaskLockUtil.java

@@ -0,0 +1,75 @@
+package com.qmth.teachcloud.mark.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TaskLockUtil {
+
+    // 评卷任务并发处理互斥锁
+    private static final Map<String, TaskLock> formalTaskMap = new HashMap<>();
+
+    // 试评任务并发处理互斥锁
+    private static final Map<String, TaskLock> trialTaskMap = new HashMap<>();
+
+    // 复核整卷并发处理互斥锁
+    private static final Map<String, TaskLock> inspectedStudentMap = new HashMap<>();
+
+    // 复核评卷任务并发处理互斥锁
+    private static final Map<String, TaskLock> inspectedLibraryMap = new HashMap<>();
+
+    public static void clearTimeoutTask(long timeoutMinute) {
+        for (TaskLock taskLock : trialTaskMap.values()) {
+            taskLock.expire(timeoutMinute);
+        }
+        for (TaskLock taskLock : formalTaskMap.values()) {
+            taskLock.expire(timeoutMinute);
+        }
+        for (TaskLock taskLock : inspectedStudentMap.values()) {
+            taskLock.expire(timeoutMinute);
+        }
+        for (TaskLock taskLock : inspectedLibraryMap.values()) {
+            taskLock.expire(timeoutMinute);
+        }
+    }
+
+    public static TaskLock getTrialTask(String key) {
+        TaskLock taskLock = trialTaskMap.get(key);
+        if (taskLock == null) {
+            synchronized (trialTaskMap) {
+                taskLock = trialTaskMap.computeIfAbsent(key, k -> new TaskLock());
+            }
+        }
+        return taskLock;
+    }
+
+    public static TaskLock getFormalTask(String key) {
+        TaskLock taskLock = formalTaskMap.get(key);
+        if (taskLock == null) {
+            synchronized (formalTaskMap) {
+                taskLock = formalTaskMap.computeIfAbsent(key, k -> new TaskLock());
+            }
+        }
+        return taskLock;
+    }
+
+    public static TaskLock getInspectedStudentTask(String key) {
+        TaskLock taskLock = inspectedStudentMap.get(key);
+        if (taskLock == null) {
+            synchronized (inspectedStudentMap) {
+                taskLock = inspectedStudentMap.computeIfAbsent(key, k -> new TaskLock());
+            }
+        }
+        return taskLock;
+    }
+
+    public static TaskLock getInspectedLibraryTask(String key) {
+        TaskLock taskLock = inspectedLibraryMap.get(key);
+        if (taskLock == null) {
+            synchronized (inspectedLibraryMap) {
+                taskLock = inspectedLibraryMap.computeIfAbsent(key, k -> new TaskLock());
+            }
+        }
+        return taskLock;
+    }
+
+}