Explorar o código

3.3.0 评卷质量

xiaofei hai 1 ano
pai
achega
b5182a4234
Modificáronse 25 ficheiros con 414 adicións e 7 borrados
  1. 3 1
      distributed-print/install/mysql/upgrade/3.3.0.sql
  2. 26 0
      distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkQualityController.java
  3. 72 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/bean/dto/mark/manage/MarkQualityChartDto.java
  4. 4 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/mapper/MarkPaperMapper.java
  5. 2 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/MarkPaperService.java
  6. 5 0
      teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/impl/MarkPaperServiceImpl.java
  7. 15 0
      teachcloud-common/src/main/resources/mapper/MarkPaperMapper.xml
  8. 12 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/entity/MarkUserGroup.java
  9. 3 1
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkTaskMapper.java
  10. 6 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkService.java
  11. 4 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkSyncService.java
  12. 2 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkTaskService.java
  13. 9 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkUserGroupService.java
  14. 59 1
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkServiceImpl.java
  15. 17 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkSyncServiceImpl.java
  16. 5 2
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkTaskServiceImpl.java
  17. 61 0
      teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserGroupServiceImpl.java
  18. 18 0
      teachcloud-mark/src/main/resources/mapper/MarkTaskMapper.xml
  19. 1 0
      teachcloud-mark/src/main/resources/mapper/MarkUserGroupMapper.xml
  20. 4 0
      teachcloud-task/pom.xml
  21. 3 1
      teachcloud-task/src/main/java/com/qmth/teachcloud/task/enums/JobEnum.java
  22. 29 0
      teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/UpdateMarkerQualityJob.java
  23. 2 0
      teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/service/JobService.java
  24. 44 1
      teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/service/impl/JobServiceImpl.java
  25. 8 0
      teachcloud-task/src/main/java/com/qmth/teachcloud/task/start/StartRunning.java

+ 3 - 1
distributed-print/install/mysql/upgrade/3.3.0.sql

@@ -69,7 +69,7 @@ INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('941', '仲裁-获取评卷状态', '/api/admin/mark/arbitrate/getStatus', 'URL', '917', '17', 'AUTH', '1', '1', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('942', '仲裁-获取配置信息', '/api/admin/mark/arbitrate/getSetting', 'URL', '917', '18', 'AUTH', '1', '1', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('943', '仲裁-保存任务', '/api/admin/mark/arbitrate/saveTask', 'URL', '917', '19', 'AUTH', '1', '1', '1');
-INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('944', '查看详情', 'Detail', 'LINK', '917', '6', 'AUTH', '928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,964,965', '1', '0', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('944', '查看详情', 'Detail', 'LINK', '917', '6', 'AUTH', '928,929,930,931,932,933,934,935,936,937,938,939,940,941,942,943,964,965,966,967', '1', '0', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('945', '结束评卷', 'Finish', 'LINK', '917', '7', 'AUTH', '927', '1', '0', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('946', '成绩检查', 'CheckScore', 'MENU', '486', '4', 'AUTH', '1', '0', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('947', '列表', 'List', 'LIST', '946', '1', 'AUTH', '952', '1', '0', '1');
@@ -91,6 +91,8 @@ INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('963', '开启/关闭分班阅', '/api/admin/mark/group/update_open_mark_class', 'URL', '897', '14', 'AUTH', '1', '1', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('964', '班级阅卷进度', '/api/admin/mark/group/class/summary', 'URL', '917', '20', 'AUTH', '1', '1', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('965', '绑定评卷员', '/api/admin/mark/marker/add', '917', '21', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('966', '评卷质量重新计算', '/api/admin/mark/quality/update', 'URL', '917', '22', 'AUTH', '1', '1', '1');
+INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('967', '评卷质量给分曲线', '/api/admin/mark/quality/chart', 'URL', '917', '23', 'AUTH', '1', '1', '1');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `sequence`, `related`, `enable`, `default_auth`, `front_display`) VALUES ('970', '扫描端', 'scan', 'MENU', '11', '971,972,973,974,975,976,977,978,979,980,981,982,983,984,985,986,987,988,989,990,991,992,993,994,995,996,997,998,999,1000,1001,1002,1003,1004,1005', '1', '0', '0');
 UPDATE `sys_privilege` SET `name` = '打印端', `related` = '200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,973,974,975' WHERE (`id` = '199');
 INSERT INTO `sys_privilege` (`id`, `name`, `url`, `type`, `parent_id`, `sequence`, `property`, `enable`, `default_auth`, `front_display`) VALUES ('971', '心跳接口', '/api/scan/server/heartbeat', 'URL', '970', '1', 'AUTH', '1', '1', '1');

+ 26 - 0
distributed-print/src/main/java/com/qmth/distributed/print/api/mark/MarkQualityController.java

@@ -3,6 +3,7 @@ package com.qmth.distributed.print.api.mark;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.qmth.boot.api.constant.ApiConstant;
+import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkQualityChartDto;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkQualityDto;
 import com.qmth.teachcloud.common.contant.SystemConstant;
 import com.qmth.teachcloud.common.util.Result;
@@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController;
 import javax.annotation.Resource;
 import javax.validation.constraints.Max;
 import javax.validation.constraints.Min;
+import java.util.List;
 
 /**
  * <p>
@@ -50,4 +52,28 @@ public class MarkQualityController {
         IPage<MarkQualityDto> markQualityDtoIPage = markUserGroupService.pageQuality(examId, paperNumber, groupNumber, userId, pageNumber, pageSize);
         return ResultUtil.ok(markQualityDtoIPage);
     }
+
+    /**
+     * 重新计算质量监控
+     */
+    @ApiOperation(value = "重新计算质量监控")
+    @RequestMapping(value = "/update", method = RequestMethod.POST)
+    public Result update(@ApiParam(value = "考试ID", required = true) @RequestParam Long examId,
+                         @ApiParam(value = "试卷编号", required = true) @RequestParam String paperNumber,
+                         @ApiParam(value = "分组号") @RequestParam(required = false) Integer groupNumber) {
+        markUserGroupService.updateQuality(examId, paperNumber, groupNumber);
+        return ResultUtil.ok(true);
+    }
+
+    /**
+     * 评卷质量列表查询
+     */
+    @ApiOperation(value = "评卷质量列表")
+    @RequestMapping(value = "/chart", method = RequestMethod.POST)
+    public Result chart(@ApiParam(value = "考试ID", required = true) @RequestParam Long examId,
+                               @ApiParam(value = "试卷编号", required = true) @RequestParam String paperNumber,
+                               @ApiParam(value = "分组号") @RequestParam(required = false) Integer groupNumber) {
+        List<MarkQualityChartDto> markQualityChartDtoList = markUserGroupService.listQualityChart(examId, paperNumber, groupNumber);
+        return ResultUtil.ok(markQualityChartDtoList);
+    }
 }

+ 72 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/bean/dto/mark/manage/MarkQualityChartDto.java

@@ -0,0 +1,72 @@
+package com.qmth.teachcloud.common.bean.dto.mark.manage;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+
+public class MarkQualityChartDto {
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long userId;
+    private String loginName;
+    private String name;
+    private Double totalAvgScore;
+    private Double avgScore;
+    private Double maxScore;
+    private Double minScore;
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getLoginName() {
+        return loginName;
+    }
+
+    public void setLoginName(String loginName) {
+        this.loginName = loginName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Double getTotalAvgScore() {
+        return totalAvgScore;
+    }
+
+    public void setTotalAvgScore(Double totalAvgScore) {
+        this.totalAvgScore = totalAvgScore;
+    }
+
+    public Double getAvgScore() {
+        return avgScore;
+    }
+
+    public void setAvgScore(Double avgScore) {
+        this.avgScore = avgScore;
+    }
+
+    public Double getMaxScore() {
+        return maxScore;
+    }
+
+    public void setMaxScore(Double maxScore) {
+        this.maxScore = maxScore;
+    }
+
+    public Double getMinScore() {
+        return minScore;
+    }
+
+    public void setMinScore(Double minScore) {
+        this.minScore = minScore;
+    }
+}

+ 4 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/mapper/MarkPaperMapper.java

@@ -5,8 +5,11 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.qmth.teachcloud.common.bean.dto.mark.setting.MarkSettingDto;
 import com.qmth.teachcloud.common.entity.MarkPaper;
+import com.qmth.teachcloud.common.enums.mark.MarkPaperStatus;
 import org.apache.ibatis.annotations.Param;
 
+import java.util.List;
+
 /**
  * <p>
  * 考试科目表 Mapper 接口
@@ -19,4 +22,5 @@ public interface MarkPaperMapper extends BaseMapper<MarkPaper> {
 
     IPage<MarkSettingDto> listPaperSetting(@Param("page") Page<MarkSettingDto> page, @Param("examId") Long examId, @Param("courseCode") String courseCode, @Param("paperNumber") String paperNumber, @Param("groupStatus") Boolean groupStatus);
 
+    List<MarkPaper> listQualityMarkPaperByStatus(@Param("status") String status, @Param("uploadCount") int uploadCount);
 }

+ 2 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/MarkPaperService.java

@@ -25,4 +25,6 @@ public interface MarkPaperService extends IService<MarkPaper> {
     void savePaperSetting(MarkPaper markPaper);
 
     Boolean finishPaper(Long examId, List<String> paperNumbers, MarkPaperStatus status);
+
+    List<MarkPaper> listQualityMarkPaperByStatus(MarkPaperStatus formal, int uploadCount);
 }

+ 5 - 0
teachcloud-common/src/main/java/com/qmth/teachcloud/common/service/impl/MarkPaperServiceImpl.java

@@ -77,4 +77,9 @@ public class MarkPaperServiceImpl extends ServiceImpl<MarkPaperMapper, MarkPaper
         return this.update(updateWrapper);
     }
 
+    @Override
+    public List<MarkPaper> listQualityMarkPaperByStatus(MarkPaperStatus status, int uploadCount) {
+        return this.baseMapper.listQualityMarkPaperByStatus(status.name(), uploadCount);
+    }
+
 }

+ 15 - 0
teachcloud-common/src/main/resources/mapper/MarkPaperMapper.xml

@@ -50,4 +50,19 @@
                 and mp.group_status = #{groupStatus}
             </if>
     </select>
+    <select id="listQualityMarkPaperByStatus" resultType="com.qmth.teachcloud.common.entity.MarkPaper">
+        SELECT
+            exam_id examId,
+            paper_number paperNumber
+        FROM
+            mark_paper mp
+        WHERE
+            mp.status = #{status} AND upload_count > #{uploadCount}
+          AND EXISTS( SELECT
+                          1
+                      FROM
+                          basic_exam be
+                      WHERE
+                          be.enable = 1 AND mp.exam_id = be.id)
+    </select>
 </mapper>

+ 12 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/entity/MarkUserGroup.java

@@ -52,6 +52,9 @@ public class MarkUserGroup implements Serializable {
     @ApiModelProperty(value = "完成数量")
     @TableField(updateStrategy = FieldStrategy.IGNORED)
     private Integer finishCount;
+    @ApiModelProperty(value = "有效数量")
+    @TableField(updateStrategy = FieldStrategy.IGNORED)
+    private Integer validCount;
     @ApiModelProperty(value = "管理员直接打分数量")
     @TableField(updateStrategy = FieldStrategy.IGNORED)
     private Integer headerFinishCount;
@@ -142,6 +145,14 @@ public class MarkUserGroup implements Serializable {
         this.finishCount = finishCount;
     }
 
+    public Integer getValidCount() {
+        return validCount;
+    }
+
+    public void setValidCount(Integer validCount) {
+        this.validCount = validCount;
+    }
+
     public Integer getHeaderFinishCount() {
         return headerFinishCount;
     }
@@ -200,6 +211,7 @@ public class MarkUserGroup implements Serializable {
             ", mode=" + mode +
             ", topCount=" + topCount +
             ", finishCount=" + finishCount +
+            ", validCount=" + validCount +
             ", headerFinishCount=" + headerFinishCount +
             ", avgScore=" + avgScore +
             ", maxScore=" + maxScore +

+ 3 - 1
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/mapper/MarkTaskMapper.java

@@ -1,11 +1,11 @@
 package com.qmth.teachcloud.mark.mapper;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkManageDto;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkerInfoDto;
 import com.qmth.teachcloud.mark.entity.MarkTask;
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Param;
 
 import java.util.List;
@@ -23,4 +23,6 @@ public interface MarkTaskMapper extends BaseMapper<MarkTask> {
     IPage<MarkManageDto> listPaperManage(@Param("page") Page<MarkManageDto> page, @Param("examId") Long examId, @Param("courseCode") String courseCode, @Param("paperNumber") String paperNumber, @Param("progressStatus") Boolean progressStatus);
 
     List<MarkerInfoDto> listUserMarkedCount(@Param("examId") Long examId, @Param("courseCode") String courseCode, @Param("paperNumber") String paperNumber);
+
+    MarkTask getLastOneByUserIdAndStatus(@Param("examId") Long examId, @Param("paperNumber") String paperNumber, @Param("groupNumber") Integer groupNumber, @Param("userId") Long userId, @Param("name") String name);
 }

+ 6 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkService.java

@@ -21,4 +21,10 @@ public interface MarkService {
     void updateMarkedCount(Long examId, String paperNumber, Integer groupNumber);
 
     boolean rejectMarkTask(MarkTask markTask, Long userId);
+
+    String getGroupKey(MarkGroup markGroup);
+
+    void updateQuality(MarkUserGroup markUserGroup);
+
+    boolean needUpdateQuality(MarkUserGroup marker, int expireMinutes);
 }

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

@@ -1,13 +1,17 @@
 package com.qmth.teachcloud.mark.service;
 
+import com.qmth.teachcloud.common.bean.dto.mark.setting.MarkUser;
 import com.qmth.teachcloud.mark.entity.MarkUserGroup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.List;
+
 public interface MarkSyncService {
 
     public static Logger log = LoggerFactory.getLogger(MarkSyncService.class);
 
     void markerResetSync(MarkUserGroup markUserGroup);
 
+    void updateQuality(List<MarkUserGroup> markUserGroups, String lockKey);
 }

+ 2 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/MarkTaskService.java

@@ -38,4 +38,6 @@ public interface MarkTaskService extends IService<MarkTask> {
     boolean resetById(Long markTaskId, Long userId, Long rejecterId, Long date, MarkTaskStatus newStatus);
 
     int countByExamIdAndPaperNumberAndGroupNumberAndStatusIn(Long examId, String paperNumber, Integer groupNumber, List<MarkTaskStatus> statusList);
+
+    MarkTask getLastOneByUserIdAndStatus(Long examId, String paperNumber, Integer groupNumber, Long userId, MarkTaskStatus status);
 }

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

@@ -2,6 +2,7 @@ package com.qmth.teachcloud.mark.service;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.qmth.teachcloud.common.bean.dto.mark.entrance.MarkEntranceDto;
+import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkQualityChartDto;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkQualityDto;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkUserGroupProgressDto;
 import com.qmth.teachcloud.common.bean.dto.mark.setting.MarkUser;
@@ -41,4 +42,12 @@ public interface MarkUserGroupService extends IService<MarkUserGroup> {
     void resetById(Long id);
 
     IPage<MarkQualityDto> pageQuality(Long examId, String paperNumber, Integer groupNumber, Long userId, Integer pageNumber, Integer pageSize);
+
+    void updateQuality(Long examId, String paperNumber, Integer groupNumber);
+
+    List<MarkUserGroup> listByExamIdAndPaperNumberAndGroupNumber(Long examId, String paperNumber, Integer groupNumber);
+
+    void updateQualityById(Long markUserGroupId, int finishCount, int validCount, double v, double avgScore, double stdevScore);
+
+    List<MarkQualityChartDto> listQualityChart(Long examId, String paperNumber, Integer groupNumber);
 }

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

@@ -21,10 +21,16 @@ import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 @Service
 public class MarkServiceImpl implements MarkService {
 
+    public static final int UN_SELECTIVE_SCORE = -1;
+
+    private Map<Long, Long> markerLastUpdateTime = new ConcurrentHashMap<>();
+
     @Resource
     private MarkPaperService markPaperService;
     @Resource
@@ -163,10 +169,62 @@ public class MarkServiceImpl implements MarkService {
         markGroupStudentService.save(gs);
     }
 
-    private String getGroupKey(MarkGroup markGroup) {
+    @Override
+    public String getGroupKey(MarkGroup markGroup) {
         return markGroup.getExamId() + "_" + markGroup.getPaperNumber() + "_" + markGroup.getNumber();
     }
 
+    @Override
+    public void updateQuality(MarkUserGroup markUserGroup) {
+        List<MarkTask> list = markTaskService.listByExamIdAndPaperNumberAndGroupNumberAndUserId(markUserGroup.getExamId(), markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber(), markUserGroup.getUserId());
+        int finishCount = 0;
+        int validCount = 0;
+        double sumScore = 0;
+        double sumScore2 = 0;
+        double avgScore = 0;
+        double stdevScore = 0;
+        double sumSpent = 0;
+        double avgSpent = 0;
+        for (MarkTask markTask : list) {
+            finishCount++;
+            if (markTask.getStatus() == MarkTaskStatus.MARKED) {
+                validCount++;
+            }
+            double score = markTask.getMarkerScore() != null && markTask.getMarkerScore() != UN_SELECTIVE_SCORE ? markTask.getMarkerScore() : 0;
+            int spent = markTask.getMarkerSpent() != null ? markTask.getMarkerSpent() : 0;
+
+            sumScore += score;
+            sumScore2 += Math.pow(score, 2);
+            sumSpent += spent;
+            if (finishCount > 0) {
+                avgScore = sumScore / finishCount;
+                avgSpent = sumSpent / finishCount;
+                // 递归法计算标准差
+                stdevScore = Math.sqrt(sumScore2 / finishCount - Math.pow(sumScore / finishCount, 2));
+            }
+        }
+        markUserGroupService.updateQualityById(markUserGroup.getId(), finishCount, validCount, avgSpent / 1000, avgScore, stdevScore);
+        markerLastUpdateTime.put(markUserGroup.getId(), System.currentTimeMillis());
+    }
+
+    @Override
+    public boolean needUpdateQuality(MarkUserGroup markUserGroup, int expireMinutes) {
+        if (markUserGroup == null) {
+            return false;
+        }
+        Long lastUpdateTime = markerLastUpdateTime.get(markUserGroup.getId());
+        MarkTask markTask = markTaskService.getLastOneByUserIdAndStatus(markUserGroup.getExamId(), markUserGroup.getPaperNumber(), markUserGroup.getGroupNumber(), markUserGroup.getUserId(), MarkTaskStatus.MARKED);
+        if (markTask != null && markTask.getMarkerTime() != null) {
+            long lastMarkTime = markTask.getMarkerTime();
+            if (lastUpdateTime != null && lastUpdateTime > lastMarkTime) {
+                return false;
+            } else {
+                return (System.currentTimeMillis() - lastMarkTime) < (expireMinutes * 60 * 1000);
+            }
+        }
+        return false;
+    }
+
     private String getGroupKey(MarkUserGroup markUserGroup) {
         return markUserGroup.getExamId() + "_" + markUserGroup.getPaperNumber() + "_" + markUserGroup.getGroupNumber();
     }

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

@@ -5,10 +5,12 @@ 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.apache.commons.collections4.CollectionUtils;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.util.List;
 
 @Service
 public class MarkSyncServiceImpl implements MarkSyncService {
@@ -42,4 +44,19 @@ public class MarkSyncServiceImpl implements MarkSyncService {
             lockService.unlock(LockType.MARKER_RESET, markUserGroup.getId());
         }
     }
+
+    @Async
+    @Override
+    public void updateQuality(List<MarkUserGroup> markUserGroups, String lockKey) {
+        if (CollectionUtils.isNotEmpty(markUserGroups)) {
+            for (MarkUserGroup markUserGroup : markUserGroups) {
+                try {
+                    markService.updateQuality(markUserGroup);
+                } catch (Exception e) {
+                    log.error("marker quality thread error", e);
+                }
+            }
+        }
+        lockService.unlock(LockType.BATCH_QUALITY, lockKey);
+    }
 }

+ 5 - 2
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkTaskServiceImpl.java

@@ -1,7 +1,6 @@
 package com.qmth.teachcloud.mark.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.Update;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -23,7 +22,6 @@ import org.springframework.util.CollectionUtils;
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import java.text.DecimalFormat;
-import java.util.Date;
 import java.util.List;
 
 /**
@@ -150,4 +148,9 @@ public class MarkTaskServiceImpl extends ServiceImpl<MarkTaskMapper, MarkTask> i
                 .in(MarkTask::getStatus, statusList);
         return this.count(queryWrapper);
     }
+
+    @Override
+    public MarkTask getLastOneByUserIdAndStatus(Long examId, String paperNumber, Integer groupNumber, Long userId, MarkTaskStatus status) {
+        return this.baseMapper.getLastOneByUserIdAndStatus(examId, paperNumber, groupNumber, userId, status.name());
+    }
 }

+ 61 - 0
teachcloud-mark/src/main/java/com/qmth/teachcloud/mark/service/impl/MarkUserGroupServiceImpl.java

@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.teachcloud.common.bean.dto.mark.entrance.MarkEntranceDto;
+import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkQualityChartDto;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkQualityDto;
 import com.qmth.teachcloud.common.bean.dto.mark.manage.MarkUserGroupProgressDto;
 import com.qmth.teachcloud.common.bean.dto.mark.setting.MarkUser;
@@ -33,6 +34,7 @@ import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * <p>
@@ -213,4 +215,63 @@ public class MarkUserGroupServiceImpl extends ServiceImpl<MarkUserGroupMapper, M
         }
         return markQualityDtoIPage;
     }
+
+    @Override
+    public void updateQuality(Long examId, String paperNumber, Integer groupNumber) {
+        if (groupNumber == null) {
+            throw ExceptionResultEnum.ERROR.exception("请选择评阅题目");
+        }
+        MarkGroup markGroup = markGroupService.getByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
+        if (markGroup != null) {
+            String lockKey = markService.getGroupKey(markGroup);
+            if (lockService.trylock(LockType.BATCH_QUALITY, lockKey)) {
+                markSyncService.updateQuality(this.listByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber), lockKey);
+            }
+        }
+    }
+
+    @Override
+    public List<MarkUserGroup> listByExamIdAndPaperNumberAndGroupNumber(Long examId, String paperNumber, Integer groupNumber) {
+        QueryWrapper<MarkUserGroup> queryWrapper = new QueryWrapper<>();
+        queryWrapper.lambda().eq(MarkUserGroup::getExamId, examId)
+                .eq(MarkUserGroup::getPaperNumber, paperNumber)
+                .eq(MarkUserGroup::getGroupNumber, groupNumber)
+                .orderByAsc(MarkUserGroup::getId);
+        return this.list(queryWrapper);
+    }
+
+    @Override
+    public void updateQualityById(Long markUserGroupId, int finishCount, int validCount, double avgSpeed, double avgScore, double stdevScore) {
+        UpdateWrapper<MarkUserGroup> updateWrapper = new UpdateWrapper<>();
+        updateWrapper.lambda().set(MarkUserGroup::getFinishCount, finishCount)
+                .set(MarkUserGroup::getValidCount, validCount)
+                .set(MarkUserGroup::getAvgSpeed, avgSpeed)
+                .set(MarkUserGroup::getAvgScore, avgScore)
+                .set(MarkUserGroup::getStdevScore, stdevScore)
+                .eq(MarkUserGroup::getId, markUserGroupId);
+        this.update(updateWrapper);
+    }
+
+    @Override
+    public List<MarkQualityChartDto> listQualityChart(Long examId, String paperNumber, Integer groupNumber) {
+        if (groupNumber == null) {
+            throw ExceptionResultEnum.ERROR.exception("请选择对应的科目及分组,再查看给分曲线");
+        }
+        List<MarkQualityChartDto> markQualityChartDtoList = new ArrayList<>();
+        List<MarkUserGroup> markUserGroupList = this.listByExamIdAndPaperNumberAndGroupNumber(examId, paperNumber, groupNumber);
+        Double totalAvgScore = markUserGroupList.stream().collect(Collectors.averagingDouble(MarkUserGroup::getAvgScore));
+        for (MarkUserGroup markUserGroup : markUserGroupList) {
+            MarkQualityChartDto markQualityChartDto = new MarkQualityChartDto();
+            markQualityChartDto.setUserId(markUserGroup.getUserId());
+            SysUser sysUser = sysUserService.getById(markUserGroup.getUserId());
+            markQualityChartDto.setLoginName(sysUser.getLoginName());
+            markQualityChartDto.setName(sysUser.getRealName());
+            markQualityChartDto.setTotalAvgScore(Double.parseDouble(new DecimalFormat("####.###").format(totalAvgScore)));
+            markQualityChartDto.setAvgScore(markUserGroup.getAvgScore());
+            markQualityChartDto.setMaxScore(markUserGroup.getMaxScore());
+            markQualityChartDto.setMinScore(markUserGroup.getMinScore());
+            markQualityChartDtoList.add(markQualityChartDto);
+        }
+        return markQualityChartDtoList;
+    }
 }

+ 18 - 0
teachcloud-mark/src/main/resources/mapper/MarkTaskMapper.xml

@@ -85,5 +85,23 @@
         </where>
         GROUP BY mt.exam_id , mt.course_code , mt.course_name , mt.paper_number , mt.group_number , su.login_name , su.code , su.real_name
     </select>
+    <select id="getLastOneByUserIdAndStatus" resultType="com.qmth.teachcloud.mark.entity.MarkTask">
+        SELECT
+            exam_id examId,
+            paper_number paperNumber,
+            group_number groupNumber,
+            user_id userId,
+            marker_time markerTime
+        FROM
+            mark_task
+        WHERE
+            exam_id = #{examId}
+          AND paper_number = #{paperNumber}
+          AND group_number = #{groupNumber}
+          AND user_id = #{userId}
+          AND status = #{status}
+        ORDER BY marker_time DESC
+            LIMIT 1
+    </select>
 
 </mapper>

+ 1 - 0
teachcloud-mark/src/main/resources/mapper/MarkUserGroupMapper.xml

@@ -12,6 +12,7 @@
         <result column="mode" property="mode" />
         <result column="top_count" property="topCount" />
         <result column="finish_count" property="finishCount" />
+        <result column="valid_count" property="validCount" />
         <result column="header_finish_count" property="headerFinishCount" />
         <result column="avg_score" property="avgScore" />
         <result column="max_score" property="maxScore" />

+ 4 - 0
teachcloud-task/pom.xml

@@ -18,6 +18,10 @@
             <groupId>com.qmth.distributed.print.business</groupId>
             <artifactId>distributed-print-business</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.qmth.teachcloud.mark</groupId>
+            <artifactId>teachcloud-mark</artifactId>
+        </dependency>
 <!--        <dependency>-->
 <!--            <groupId>com.qmth.teachcloud.report.business</groupId>-->
 <!--            <artifactId>teachcloud-report-business</artifactId>-->

+ 3 - 1
teachcloud-task/src/main/java/com/qmth/teachcloud/task/enums/JobEnum.java

@@ -37,7 +37,9 @@ public enum JobEnum {
 
     REDIS_MQ_JOB("学校信息同步定时任务"),
 
-    REDIS_MQ_JOB_GROUP("学校信息同步定时任务组");
+    REDIS_MQ_JOB_GROUP("学校信息同步定时任务组"),
+    UPDATE_MARKER_QUALITY("更新评卷员质量监控指标"),
+    UPDATE_MARKER_QUALITY_GROUP("更新评卷员质量监控指标任务组");
 
     private String title;
 

+ 29 - 0
teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/UpdateMarkerQualityJob.java

@@ -0,0 +1,29 @@
+package com.qmth.teachcloud.task.job;
+
+import com.qmth.teachcloud.common.contant.SystemConstant;
+import com.qmth.teachcloud.task.job.service.JobService;
+import org.quartz.JobExecutionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.quartz.QuartzJobBean;
+
+import javax.annotation.Resource;
+
+/**
+ * 更新评卷员质量监控指标定时任务
+ */
+public class UpdateMarkerQualityJob extends QuartzJobBean {
+    private final static Logger log = LoggerFactory.getLogger(UpdateMarkerQualityJob.class);
+
+    @Resource
+    JobService jobService;
+
+    @Override
+    protected void executeInternal(JobExecutionContext jobExecutionContext) {
+        try {
+            jobService.updateMarkerQuality();
+        } catch (Exception e) {
+            log.error(SystemConstant.LOG_ERROR, e);
+        }
+    }
+}

+ 2 - 0
teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/service/JobService.java

@@ -49,4 +49,6 @@ public interface JobService {
      * 机器心跳
      */
     void machineHeart();
+
+    void updateMarkerQuality();
 }

+ 44 - 1
teachcloud-task/src/main/java/com/qmth/teachcloud/task/job/service/impl/JobServiceImpl.java

@@ -4,7 +4,15 @@ import com.qmth.boot.redis.uid.RedisMachineService;
 import com.qmth.distributed.print.business.service.*;
 import com.qmth.teachcloud.common.bean.dto.MqDto;
 import com.qmth.teachcloud.common.contant.SystemConstant;
+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.common.util.RedisUtil;
+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.task.job.service.JobService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -12,6 +20,7 @@ import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
 import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
@@ -40,7 +49,14 @@ public class JobServiceImpl implements JobService {
     ExamPaperStructureService examPaperStructureService;
 
     @Resource
-    ExamPrintPlanService examPrintPlanService;
+    MarkPaperService markPaperService;
+
+    @Resource
+    MarkGroupService markGroupService;
+    @Resource
+    MarkUserGroupService markUserGroupService;
+    @Resource
+    MarkService markService;
 
     @Resource
     RedisUtil redisUtil;
@@ -82,6 +98,33 @@ public class JobServiceImpl implements JobService {
     public void machineHeart() {
         redisUtil.set(SystemConstant.TASK_MACHINE_ID + redisMachineService.getMachineId(), redisMachineService.getMachineId(), 30, TimeUnit.SECONDS);
     }
+    @Override
+    public void updateMarkerQuality() {
+        log.info("start auto-update marker");
+        try {
+            List<MarkPaper> markPaperList = markPaperService.listQualityMarkPaperByStatus(MarkPaperStatus.FORMAL, 0);
+            for (MarkPaper markPaper : markPaperList) {
+                List<MarkGroup> markGroups = markGroupService.listGroupByExamIdAndPaperNumber(markPaper.getExamId(), markPaper.getPaperNumber());
+                for (MarkGroup markGroup : markGroups) {
+                    List<MarkUserGroup> markUserGroups = markUserGroupService.listByExamIdAndPaperNumberAndGroupNumber(markPaper.getExamId(), markPaper.getPaperNumber(), markGroup.getNumber());
+                    for (MarkUserGroup marker : markUserGroups) {
+                        try {
+                            if (markService.needUpdateQuality(marker, 30)) {
+                                log.info("start auto-update markerId: " + marker.getId());
+                                markService.updateQuality(marker);
+                            }
+                        } catch (Exception e) {
+                            log.error("update marker quality error for markerId=" + marker.getId(), e);
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("auto-update marker error", e);
+        } finally {
+            log.info("finish auto-update marker");
+        }
+    }
 
     /**
      * 组装job

+ 8 - 0
teachcloud-task/src/main/java/com/qmth/teachcloud/task/start/StartRunning.java

@@ -88,6 +88,14 @@ public class StartRunning implements CommandLineRunner {
         quartzService.addJob(SubjectCalculateJob.class, JobEnum.SYNC_REUNIFY_JOB.name(), JobEnum.SYNC_REUNIFY_JOB_GROUP.name(), "0 0 0/2 * * ?", reunifyJobMap);
         log.info("增加自动统分定时任务 end");
 
+        // 每1分钟一次
+        log.info("增加更新评卷员质量监控指标定时任务 start");
+        Map qualityJobMap = new HashMap();
+        qualityJobMap.computeIfAbsent("name", v -> SubjectCalculateJob.class.getName());
+        quartzService.deleteJob(JobEnum.UPDATE_MARKER_QUALITY.name(), JobEnum.UPDATE_MARKER_QUALITY_GROUP.name());
+        quartzService.addJob(SubjectCalculateJob.class, JobEnum.UPDATE_MARKER_QUALITY.name(), JobEnum.UPDATE_MARKER_QUALITY_GROUP.name(), "0 */1 * * * ?", qualityJobMap);
+        log.info("增加更新评卷员质量监控指标定时任务 end");
+
         log.info("服务器启动时执行 end");
     }
 }