فهرست منبع

机构版-迭代

xiaof 4 سال پیش
والد
کامیت
887879a970
42فایلهای تغییر یافته به همراه975 افزوده شده و 97 حذف شده
  1. 41 28
      stmms-ms-accesscontrol/src/main/java/cn/com/qmth/stmms/ms/accesscontrol/LoginInterceptor.java
  2. 18 8
      stmms-ms-accesscontrol/src/main/java/cn/com/qmth/stmms/ms/accesscontrol/api/AuthApi.java
  3. 9 1
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/ParamApi.java
  4. 30 0
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/StudentApi.java
  5. 3 0
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/UserApi.java
  6. 25 6
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/WorkApi.java
  7. 32 0
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/dto/StudentMissingDTO.java
  8. 26 17
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/exporter/ScoreExporter.java
  9. 30 1
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/exporter/UserExporter.java
  10. 15 0
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/importer/StudentImporter.java
  11. 88 13
      stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/service/DataUploadService.java
  12. 25 3
      stmms-ms-collect/src/main/java/cn/com/qmth/stmms/ms/collect/api/CollectApi.java
  13. 58 0
      stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/config/DbConfig.java
  14. 22 8
      stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/utils/AesUtil.java
  15. 63 0
      stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/utils/DbBackupUtils.java
  16. 40 0
      stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/utils/ServletUtil.java
  17. 13 0
      stmms-ms-core/pom.xml
  18. 54 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/cache/CacheService.java
  19. 10 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/Paper.java
  20. 44 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/ParamSetting.java
  21. 33 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/Student.java
  22. 10 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/task/MarkTask.java
  23. 10 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/user/MarkUser.java
  24. 5 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/MarkTaskRepo.java
  25. 2 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/MarkUserRepo.java
  26. 3 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/PaperRepo.java
  27. 7 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/StudentRepo.java
  28. 8 0
      stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/specification/StudentSpecification.java
  29. 33 2
      stmms-ms-log/src/main/java/cn/com/qmth/stmms/ms/log/aop/MarkLogAop.java
  30. 2 0
      stmms-ms-main/src/main/java/cn/com/qmth/stmms/ms/Application.java
  31. 11 5
      stmms-ms-main/src/main/resources/application-dev.properties
  32. 1 1
      stmms-ms-main/src/main/resources/application.properties
  33. 45 0
      stmms-ms-main/src/main/resources/ehcache.xml
  34. 39 0
      stmms-ms-main/src/test/CacheTest.java
  35. 7 0
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/MakrerApi.java
  36. 8 0
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/MarkSubjectApi.java
  37. 32 1
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/MarkTaskApi.java
  38. 45 2
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/PaperApi.java
  39. 1 1
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/assembler/PaperAssembler.java
  40. 1 0
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/dto/LevelStatDTO.java
  41. 25 0
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/service/AssignTaskService.java
  42. 1 0
      stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/service/MarkingService.java

+ 41 - 28
stmms-ms-accesscontrol/src/main/java/cn/com/qmth/stmms/ms/accesscontrol/LoginInterceptor.java

@@ -1,12 +1,15 @@
 package cn.com.qmth.stmms.ms.accesscontrol;
 
 import cn.com.qmth.stmms.ms.accesscontrol.config.LoginConfig;
+import cn.com.qmth.stmms.ms.commons.utils.AesUtil;
+import cn.com.qmth.stmms.ms.core.cache.CacheService;
 import cn.com.qmth.stmms.ms.core.domain.user.AbstractUser;
 import cn.com.qmth.stmms.ms.core.domain.user.MarkUser;
 import cn.com.qmth.stmms.ms.core.domain.user.Role;
 import cn.com.qmth.stmms.ms.core.repository.MarkUserRepo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
+import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
 
 import javax.servlet.http.HttpServletRequest;
@@ -21,38 +24,48 @@ public class LoginInterceptor extends HandlerInterceptorAdapter {
     @Autowired
     private LoginConfig loginConfig;
 
+    @Autowired
+    private CacheService cacheService;
+
     @Override
     public boolean preHandle(HttpServletRequest request,
                              HttpServletResponse response, Object handler) throws Exception {
-        String token = (String) request.getSession().getAttribute("token");
-        if (token != null) {
-            String[] strs = token.split("#");
-            String loginName = strs[1];
-            if (loginConfig.adminLoginConfig().getLoginName().equals(loginName)
-//                    || loginConfig.inspectionLoginConfig().getLoginName().equals(loginName)
-            ) {
-                return true;
-            }
-//            else if (loginConfig.clientLoginConfig().getLoginName().equals(loginName)) {
-//                response.sendError(HttpStatus.UNAUTHORIZED.value(), "该账户只能在采集端登录");
-//                return false;
-//            }
-//            else if (loginConfig.uploadLoginConfig().getLoginName().equals(loginName)) {
-//                response.sendError(HttpStatus.UNAUTHORIZED.value(), "该账户只能在图片客户端登录");
-//                return false;
-//            }
-            AbstractUser user = markUserRepo.findByLoginName(loginName);
-            if (Objects.equals(Role.COLLECTOR, ((MarkUser) user).getRole())) {
-                response.sendError(HttpStatus.UNAUTHORIZED.value(), "该账户只能在采集端登录");
-                return false;
-            }
-            String sessionId = request.getSession().getId();
-            if (user.getSessionId().equalsIgnoreCase(sessionId)) {
-                return true;
-            }
+        String authorization = request.getHeader("Authorization");
+        if(StringUtils.isEmpty(authorization)){
+            authorization = request.getParameter("Authorization");
+        }
+        String userId = request.getHeader("userId");
+        if(StringUtils.isEmpty(userId)){
+            userId = request.getParameter("userId");
+        }
+        String token = cacheService.queryTokenCache(userId);
+        if (StringUtils.isEmpty(authorization) || StringUtils.isEmpty(userId) || StringUtils.isEmpty(token)) {
+            response.sendError(HttpStatus.UNAUTHORIZED.value(), "身份验证失效,请重新登录!");
+            return false;
+        }
+
+        String tokenDec = AesUtil.decoder(token, "token", null);
+        String[] strs = tokenDec.split("#");
+        String loginName = strs[1];
+        if (loginConfig.adminLoginConfig().getLoginName().equals(loginName)) {
+            return true;
+        }
+        AbstractUser user = markUserRepo.findOne(Long.valueOf(userId));
+        if (Objects.equals(Role.COLLECTOR, ((MarkUser) user).getRole())) {
+            response.sendError(HttpStatus.UNAUTHORIZED.value(), "该账户只能在采集端登录");
+            return false;
+        }
+
+        if(!Objects.equals(Role.QC, ((MarkUser) user).getRole()) && !authorization.equals(token)){
+            response.sendError(HttpStatus.UNAUTHORIZED.value(), "该账号在其它地方登录");
+            return false;
+        }
+
+        String sessionId = request.getSession().getId();
+        if (user.getSessionId().equalsIgnoreCase(sessionId)) {
+            return true;
         }
-        response.sendError(HttpStatus.UNAUTHORIZED.value(), "没有登录或会话超时");
-        return false;
+        return true;
     }
 
     public void postHandle(HttpServletRequest request,

+ 18 - 8
stmms-ms-accesscontrol/src/main/java/cn/com/qmth/stmms/ms/accesscontrol/api/AuthApi.java

@@ -1,6 +1,8 @@
 package cn.com.qmth.stmms.ms.accesscontrol.api;
 
 import cn.com.qmth.stmms.ms.accesscontrol.config.LoginConfig;
+import cn.com.qmth.stmms.ms.commons.utils.AesUtil;
+import cn.com.qmth.stmms.ms.core.cache.CacheService;
 import cn.com.qmth.stmms.ms.core.domain.user.MarkUser;
 import cn.com.qmth.stmms.ms.core.domain.user.Role;
 import cn.com.qmth.stmms.ms.core.repository.MarkUserRepo;
@@ -25,14 +27,18 @@ public class AuthApi {
     @Autowired
     private MarkUserRepo markUserRepo;
 
+    @Autowired
+    private CacheService cacheService;
+
     @RequestMapping(value = "/login", method = RequestMethod.POST)
-    public MarkUser login(MarkUser user, HttpServletRequest request) {
+    public MarkUser login(MarkUser user, HttpServletRequest request) throws Exception {
         MarkUser domain = null;
 
 
         if (loginConfig.adminLoginConfig().getLoginName().equals(user.getLoginName()) &&
                 loginConfig.adminLoginConfig().getPassword().equals(user.getPassword())) {
             domain = new MarkUser(loginConfig.adminLoginConfig().getLoginName(), loginConfig.adminLoginConfig().getPassword(), null, null, "系统管理员", Role.SUPER_ADMIN, null);
+            domain.setId(0l);
         }
 //        else if (loginConfig.clientLoginConfig().getLoginName().equals(user.getLoginName()) &&
 //                loginConfig.clientLoginConfig().getPassword().equals(user.getPassword())) {
@@ -75,14 +81,18 @@ public class AuthApi {
             domain.setSessionId(request.getSession().getId());
             markUserRepo.save(domain);
         }
-        HttpSession session = request.getSession();
-        String token = (String) request.getSession().getAttribute("token");
-        if (!StringUtils.isEmpty(token)) {
-            session.setAttribute("token", null);
-        }
+//        HttpSession session = request.getSession();
+//        String token = (String) request.getSession().getAttribute("token");
+//        if (!StringUtils.isEmpty(token)) {
+//            session.setAttribute("token", null);
+//        }
         //session过期时间
-        session.setMaxInactiveInterval(-1);
-        session.setAttribute("token", System.currentTimeMillis() + "#" + domain.getLoginName() + "#" + domain.getId());
+//        session.setMaxInactiveInterval(-1);
+        String token = System.currentTimeMillis() + "#" + domain.getLoginName() + "#" + domain.getId();
+        token = AesUtil.encoder(token, "token", null);
+//        session.setAttribute("token", System.currentTimeMillis() + "#" + domain.getLoginName() + "#" + domain.getId());
+        cacheService.saveTokenCache(domain.getId().toString(), token);
+        domain.setToken(token);
         return domain;
     }
 

+ 9 - 1
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/ParamApi.java

@@ -123,7 +123,10 @@ public class ParamApi {
                                            @RequestParam Integer majority,
                                            @RequestParam Integer cumulativeError,
                                            @RequestParam Integer levelShowAllPaper,
-                                           @RequestParam Integer showPaperCount) {
+                                           @RequestParam Integer showPaperCount,
+                                           @RequestParam Integer propDenominator,
+                                           @RequestParam Integer autoCallbackShowDeviation,
+                                           @RequestParam Integer showSample) {
         List<MarkSubject> markSubjects = markSubjectRepo.findByWorkIdAndTestNotIn(workId, Arrays.asList(TrialEnum.DEFAULT.ordinal(), TrialEnum.START_FORMAL.ordinal()));
         int countMarkTasks = markTaskRepo.countByWorkId(workId);
         boolean flag = false;
@@ -148,6 +151,9 @@ public class ParamApi {
         paramSetting.setCumulativeError(cumulativeError);
         paramSetting.setLevelShowAllPaper(levelShowAllPaper);
         paramSetting.setShowPaperCount(showPaperCount);
+        paramSetting.setPropDenominator(propDenominator);
+        paramSetting.setAutoCallbackShowDeviation(autoCallbackShowDeviation);
+        paramSetting.setShowSample(showSample);
         paramSettingRepo.saveAndFlush(paramSetting);
         ParamCache.resetParam(paramSetting);
 
@@ -166,6 +172,7 @@ public class ParamApi {
         Integer changeStage = (Integer) map.get("changeStage");
         Integer scoreShowAllPaper = (Integer) map.get("scoreShowAllPaper");
         Integer isLevelToScore = (Integer) map.get("isLevelToScore");
+        Integer directScore = (Integer) map.get("directScore");
         List<MarkSubject> markSubjects = markSubjectRepo.findByWorkIdAndTestNotIn(workId, Arrays.asList(TrialEnum.DEFAULT.ordinal(), TrialEnum.START_FORMAL.ordinal()));
 //        List<MarkTask> markTasks = markTaskRepo.findByWorkId(workId);
         int countMarkTasks = markTaskRepo.countByWorkId(workId);
@@ -186,6 +193,7 @@ public class ParamApi {
         paramSetting.setChangeStage(changeStage);
         paramSetting.setScoreShowAllPaper(scoreShowAllPaper);
         paramSetting.setIsLevelToScore(isLevelToScore);
+        paramSetting.setDirectScore(directScore);
         paramSettingRepo.saveAndFlush(paramSetting);
         ParamCache.resetParam(paramSetting);
 

+ 30 - 0
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/StudentApi.java

@@ -13,6 +13,8 @@ import cn.com.qmth.stmms.ms.core.vo.Subject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.dao.DataIntegrityViolationException;
 import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -192,4 +194,32 @@ public class StudentApi {
         return areaList;
     }
 
+    /**
+     * 标记缺考
+     * @param studentId
+     * @param subject
+     * @param isManual
+     * @throws Exception
+     */
+    @RequestMapping(value = "/missing", method = RequestMethod.POST)
+    public ResponseEntity addPaper(@RequestParam Long studentId,
+                                   @RequestParam Subject subject,
+                                   @RequestParam boolean isManual) {
+        Student student = studentRepo.findOne(studentId);
+        if(student == null){
+            throw new RuntimeException("考生不存在");
+        }
+        if(Subject.SC.equals(subject)){
+            student.setScMissing(isManual);
+        } else if (Subject.SX.equals(subject)){
+            student.setSxMissing(isManual);
+        } else if (Subject.SM.equals(subject)){
+            student.setSmMissing(isManual);
+        } else {
+            throw new RuntimeException("科目有误");
+        }
+        studentRepo.save(student);
+        return new ResponseEntity(true, HttpStatus.OK);
+    }
+
 }

+ 3 - 0
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/UserApi.java

@@ -81,6 +81,7 @@ public class UserApi {
         domain.setOneClickLevel(user.getOneClickLevel());
         domain.setStandardVolume(user.getStandardVolume());
         domain.setLevelCallback(user.getLevelCallback());
+        domain.setLeaderConfirm(user.getLeaderConfirm());
         markUserRepo.save(domain);
     }
 
@@ -104,6 +105,7 @@ public class UserApi {
             user.setOneClickLevel(false);
             user.setStandardVolume(false);
             user.setLevelCallback(false);
+            user.setLeaderConfirm(false);
         }
         user.setOrganizationId(organizationId);
         markUserRepo.save(user);
@@ -169,6 +171,7 @@ public class UserApi {
         user.setOneClickLevel(false);
         user.setStandardVolume(false);
         user.setLevelCallback(false);
+        user.setLeaderConfirm(false);
         user.setOrganizationId(organizationId);
         markUserRepo.save(user);
     }

+ 25 - 6
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/api/WorkApi.java

@@ -18,7 +18,9 @@ import javax.servlet.http.HttpServletRequest;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -109,16 +111,33 @@ public class WorkApi {
     @Transactional
     @RequestMapping(value = "{workId}", method = RequestMethod.PUT)
     public Work update(@PathVariable Long workId, @RequestBody Work work) {
-        /*List<MarkSubject> subjects = markSubjectRepo.findAllByWorkId(workId);
-        long count = subjects.stream().filter(m->m.getStage().ordinal() != 0).count();
-        if(count > 0){
-            throw new RuntimeException("档位参数只能采集阶段修改");
-        }*/
+        List<MarkSubject> subjects = markSubjectRepo.findAllByWorkId(workId);
+        long count = subjects.stream().filter(m -> m.getStage().ordinal() != 0).count();
+        List<Level> levels = work.getLevels();
+        if (count > 0) {
+            List<Level> levelList = levelRepo.findByWorkId(workId);
+            Map<String, Level> levelMap = levelList.stream().collect(Collectors.toMap(Level::getCode, Function.identity()));
+            if (levels.size() != levelList.size()) {
+                throw new RuntimeException("开始分档后,不允许增加或删除档位");
+            }
+            for (Level level : levels) {
+                Level oldLevel = levelMap.get(level.getCode());
+                if (oldLevel == null) {
+                    throw new RuntimeException("开始分档后,不允许增加或删除档位");
+                }
+                if (level.getMinScore().intValue() != oldLevel.getMinScore().intValue()
+                        || level.getMaxScore().intValue() != oldLevel.getMaxScore().intValue()
+                        || level.getIntervalScore() != oldLevel.getIntervalScore()
+                        || level.getWeight().intValue() != oldLevel.getWeight().intValue()
+                        || !level.getLevelType().equals(oldLevel.getLevelType())) {
+                    throw new RuntimeException("开始分档后,只允许修改阈值");
+                }
+            }
+        }
 
         levelRepo.deleteByWorkId(work.getId());
 
         //根据A~Z排序后,重新设置levelValue
-        List<Level> levels = work.getLevels();
         AtomicInteger ai = new AtomicInteger(0);
         levels.stream().sorted(Comparator.comparing(Level::getCode)).map(l->{
             l.setId(null);

+ 32 - 0
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/dto/StudentMissingDTO.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.stmms.ms.admin.dto;
+
+import cn.com.qmth.stmms.ms.commons.utils.excel.ExcelProperty;
+
+import java.io.Serializable;
+
+public class StudentMissingDTO implements Serializable {
+
+    private static final long serialVersionUID = -4556126416794102992L;
+
+    @ExcelProperty(index = 1, name = "准考证号", type = 2)
+    private String examNumber;
+
+    @ExcelProperty(index = 2, name = "姓名", type = 2)
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+}

+ 26 - 17
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/exporter/ScoreExporter.java

@@ -35,6 +35,7 @@ import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
 
 /**
  * Created by zhengmin on 2017/1/18.
@@ -210,7 +211,7 @@ public class ScoreExporter {
      * @param workId
      * @param subjectMap
      */
-    public Sheet drawExcelFixedHeadLevel(int type, Sheet sheet, CellStyle style, Row row, Map<String, Map<String, ExportLevelResultDTO>> subjectMap, Long workId) {
+    public Sheet drawExcelFixedHeadLevel(int type, Sheet sheet, CellStyle style, Row row, Map<String, Map<String, ExportLevelResultDTO>> subjectMap, Long workId, Map<String, String> markSubjectMap) {
         //准考证号
         Cell cell = row.createCell(0);
         cell.setCellValue(EXAM_NUMBER);
@@ -243,9 +244,9 @@ public class ScoreExporter {
             if (Objects.nonNull(subjectMap.get(Subject.SC.toString()))) {
                 size = subjectMap.get(Subject.SC.toString()).size();
             }
-            int scSumCount  = level + size + 5;
+            int scSumCount = level + size + 5;
             Cell cell4 = row.createCell(level);
-            cell4.setCellValue(SC_NAME);
+            cell4.setCellValue(markSubjectMap.get(Subject.SC.toString()));
             cell4.setCellStyle(style);
             if (level != scSumCount) {
                 CellRangeAddress region5 = new CellRangeAddress(0, 0, (short) level, scSumCount);
@@ -261,7 +262,7 @@ public class ScoreExporter {
             }
             int smSumCount = scSumCount + size + 5;//最终列+1,因为从0开始
             Cell cell7 = row.createCell(scSumCount);
-            cell7.setCellValue(SM_NAME);
+            cell7.setCellValue(markSubjectMap.get(Subject.SM.toString()));
             cell7.setCellStyle(style);
             if (scSumCount != smSumCount) {
                 CellRangeAddress region6 = new CellRangeAddress(0, 0, (short) scSumCount, smSumCount);
@@ -276,7 +277,7 @@ public class ScoreExporter {
             }
             int sxSumCount = smSumCount + size + 5;//最终列+1,因为从0开始
             Cell cell8 = row.createCell(smSumCount);
-            cell8.setCellValue(SX_NAME);
+            cell8.setCellValue(markSubjectMap.get(Subject.SX.toString()));
             cell8.setCellStyle(style);
             if (smSumCount != sxSumCount) {
                 CellRangeAddress region9 = new CellRangeAddress(0, 0, (short) smSumCount, sxSumCount);
@@ -318,7 +319,7 @@ public class ScoreExporter {
      * @param workId
      * @param subjectMap
      */
-    public Sheet drawExcelFixedHead(int type, Sheet sheet, CellStyle style, Row row, Map<String, Map<String, ExportLevelResultDTO>> subjectMap, Long workId) {
+    public Sheet drawExcelFixedHead(int type, Sheet sheet, CellStyle style, Row row, Map<String, Map<String, ExportLevelResultDTO>> subjectMap, Long workId, Map<String, String> markSubjectMap) {
         //准考证号
         Cell cell = row.createCell(0);
         cell.setCellValue(EXAM_NUMBER);
@@ -372,7 +373,7 @@ public class ScoreExporter {
                 scSumCount = score + size;
             }
             Cell cell6 = row.createCell(score);
-            cell6.setCellValue(SC_NAME);
+            cell6.setCellValue(markSubjectMap.get(Subject.SC.toString()));
             cell6.setCellStyle(style);
             if (score != scSumCount) {
                 CellRangeAddress region7 = new CellRangeAddress(0, 0, (short) score, scSumCount);
@@ -393,7 +394,7 @@ public class ScoreExporter {
                 smSumCount = scSumCount + size;
             }
             Cell cell7 = row.createCell(scSumCount);
-            cell7.setCellValue(SM_NAME);
+            cell7.setCellValue(markSubjectMap.get(Subject.SM.toString()));
             cell7.setCellStyle(style);
             if (scSumCount != smSumCount) {
                 CellRangeAddress region8 = new CellRangeAddress(0, 0, (short) scSumCount, smSumCount);
@@ -413,7 +414,7 @@ public class ScoreExporter {
                 sxSumCount = smSumCount + size;
             }
             Cell cell8 = row.createCell(smSumCount);
-            cell8.setCellValue(SX_NAME);
+            cell8.setCellValue(markSubjectMap.get(Subject.SX.toString()));
             cell8.setCellStyle(style);
             if (smSumCount != sxSumCount) {
                 CellRangeAddress region9 = new CellRangeAddress(0, 0, (short) smSumCount, sxSumCount);
@@ -489,8 +490,12 @@ public class ScoreExporter {
             style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); //垂直居中
             Row row = sheet.createRow(0);
 
+            // 科目名称
+            List<MarkSubject> markSubjectList = markSubjectRepo.findAllByWorkId(workId);
+            Map<String, String> markSubjectMap = markSubjectList.stream().collect(Collectors.toMap(m -> m.getSubject().name(), MarkSubject::getName));
+
             //绘制固定表头
-            sheet = drawExcelFixedHeadLevel(levelInterval, sheet, style, row, subjectMap, workId);
+            sheet = drawExcelFixedHeadLevel(levelInterval, sheet, style, row, subjectMap, workId, markSubjectMap);
 
             //绘制动态表头
             Row dynamicHeadRow = sheet.createRow(1);
@@ -499,19 +504,19 @@ public class ScoreExporter {
                 Map<String, ExportLevelResultDTO> scMap = subjectMap.get(Subject.SC.toString());
                 int size = 0;
 //                if (Objects.nonNull(scMap) && scMap.size() > 0) {
-                    drawExcelTeacher(levelInterval, scMap, sheet, style, dynamicHeadRow, new AtomicInteger(level));
-                    size = Objects.nonNull(scMap) && scMap.size() > 0 ? scMap.size() : 0;
+                drawExcelTeacher(levelInterval, scMap, sheet, style, dynamicHeadRow, new AtomicInteger(level));
+                size = Objects.nonNull(scMap) && scMap.size() > 0 ? scMap.size() : 0;
 //                }
                 //SM表头
                 Map<String, ExportLevelResultDTO> smMap = subjectMap.get(Subject.SM.toString());
 //                if (Objects.nonNull(smMap) && smMap.size() > 0) {
-                    drawExcelTeacher(levelInterval, smMap, sheet, style, dynamicHeadRow, new AtomicInteger(level + size + 6));
-                    size = size + (Objects.nonNull(smMap) && smMap.size() > 0 ? smMap.size() : 0);
+                drawExcelTeacher(levelInterval, smMap, sheet, style, dynamicHeadRow, new AtomicInteger(level + size + 6));
+                size = size + (Objects.nonNull(smMap) && smMap.size() > 0 ? smMap.size() : 0);
 //                }
                 //SX表头
                 Map<String, ExportLevelResultDTO> sxMap = subjectMap.get(Subject.SX.toString());
 //                if (Objects.nonNull(sxMap) && sxMap.size() > 0) {
-                    drawExcelTeacher(levelInterval, sxMap, sheet, style, dynamicHeadRow, new AtomicInteger(level + size + 6*2));
+                drawExcelTeacher(levelInterval, sxMap, sheet, style, dynamicHeadRow, new AtomicInteger(level + size + 6 * 2));
 //                }
                 //加载excel数据
                 AtomicInteger cellIndex = new AtomicInteger(0);
@@ -555,7 +560,7 @@ public class ScoreExporter {
                                 if (Objects.nonNull(v.getLevel())) {
                                     drawExcelFixedHead(finalSheet, style, hssfRow, new HeaderNode(v.getLevel(), firstRow, firstRow, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 1, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 1));
                                 }
-                                drawExcelFixedHead(finalSheet, style, hssfRow, new HeaderNode(Objects.nonNull(v.getUpload()) ? "是" : "否", firstRow, firstRow, level + (scMap == null ? 0 : scMap.size())  + (smMap == null ? 0 : smMap.size()) + 5 + 2, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 2));
+                                drawExcelFixedHead(finalSheet, style, hssfRow, new HeaderNode(Objects.nonNull(v.getUpload()) ? "是" : "否", firstRow, firstRow, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 2, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 2));
                                 drawExcelFixedHead(finalSheet, style, hssfRow, new HeaderNode(Objects.nonNull(v.getMissing()) && v.getMissing() ? "是" : "否", firstRow, firstRow, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 3, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 3));
                                 drawExcelFixedHead(finalSheet, style, hssfRow, new HeaderNode(Objects.nonNull(v.getSample()) && v.getSample() ? "是" : "否", firstRow, firstRow, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 4, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 4));
                                 drawExcelFixedHead(finalSheet, style, hssfRow, new HeaderNode(Objects.nonNull(v.getOneClick()) && v.getOneClick() ? "是" : "否", firstRow, firstRow, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 5, level + (scMap == null ? 0 : scMap.size()) + (smMap == null ? 0 : smMap.size()) + 5 + 5));
@@ -665,8 +670,12 @@ public class ScoreExporter {
             style.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER); //垂直居中
             Row row = sheet.createRow(0);
 
+            // 科目名称
+            List<MarkSubject> markSubjectList = markSubjectRepo.findAllByWorkId(workId);
+            Map<String, String> markSubjectMap = markSubjectList.stream().collect(Collectors.toMap(m -> m.getSubject().name(), MarkSubject::getName));
+
             //绘制固定表头
-            sheet = drawExcelFixedHead(scoreInterval, sheet, style, row, subjectMap, workId);
+            sheet = drawExcelFixedHead(scoreInterval, sheet, style, row, subjectMap, workId, markSubjectMap);
 
             //绘制动态表头
             Row dynamicHeadRow = sheet.createRow(1);

+ 30 - 1
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/exporter/UserExporter.java

@@ -6,19 +6,24 @@ import cn.com.qmth.stmms.ms.admin.dto.StudentDTO;
 import cn.com.qmth.stmms.ms.commons.config.ImageConfig;
 import cn.com.qmth.stmms.ms.commons.utils.excel.ExportUtils;
 import cn.com.qmth.stmms.ms.core.domain.MarkSubject;
+import cn.com.qmth.stmms.ms.core.domain.Student;
 import cn.com.qmth.stmms.ms.core.domain.Work;
+import cn.com.qmth.stmms.ms.core.domain.enums.TrialEnum;
 import cn.com.qmth.stmms.ms.core.domain.user.MarkUser;
 import cn.com.qmth.stmms.ms.core.repository.MarkSubjectRepo;
 import cn.com.qmth.stmms.ms.core.repository.MarkUserRepo;
 import cn.com.qmth.stmms.ms.core.repository.StudentRepo;
 import cn.com.qmth.stmms.ms.core.vo.Subject;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Stream;
 
 /**
@@ -61,7 +66,8 @@ public class UserExporter {
     @RequestMapping(value = "{work}/{subject}/export", method = RequestMethod.GET)
     public void countGropuByQuestion(@PathVariable Work work, @PathVariable Subject subject, HttpServletResponse response) {
         if (work != null) {
-            List<StudentDTO> list = new ArrayList<>();
+            Set<StudentDTO> list = new HashSet<>();
+            // 试卷标记缺考
             List<Object[]> result = studentRepo.listMissingBySubject(work.getId(), subject.toString());
             result.forEach(i -> {
                 StudentDTO studentDTO = new StudentDTO();
@@ -74,6 +80,29 @@ public class UserExporter {
                 studentDTO.setSchool(String.valueOf(i[6]));
                 list.add(studentDTO);
             });
+
+            // 导入缺考免单、考生管理标记缺考
+            List<Student> studentList = null;
+            if(Subject.SC.equals(subject)){
+                studentList = studentRepo.findByWorkIdAndTestAndScMissingIsTrue(work.getId(), String.valueOf(TrialEnum.DEFAULT.getId()));
+            } else if(Subject.SX.equals(subject)){
+                studentList = studentRepo.findByWorkIdAndTestAndSxMissingIsTrue(work.getId(), String.valueOf(TrialEnum.DEFAULT.getId()));
+            } else if(Subject.SM.equals(subject)){
+                studentList = studentRepo.findByWorkIdAndTestAndSmMissingIsTrue(work.getId(), String.valueOf(TrialEnum.DEFAULT.getId()));
+            }
+            if(!CollectionUtils.isEmpty(studentList)){
+                studentList.forEach(s -> {
+                    StudentDTO studentDTO = new StudentDTO();
+                    studentDTO.setExamNumber(s.getExamNumber());
+                    studentDTO.setName(s.getName());
+                    studentDTO.setAreaName(s.getAreaName());
+                    studentDTO.setAreaCode(s.getAreaCode());
+                    studentDTO.setExamRoom(s.getExamRoom());
+                    studentDTO.setSourceName(s.getSourceName());
+                    studentDTO.setSchool(s.getSchool());
+                    list.add(studentDTO);
+                });
+            }
             String fileName = null;
             if (!imageConfig.isCustomSubject()) {
                 fileName = subject.toString() + "缺考名单";

+ 15 - 0
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/importer/StudentImporter.java

@@ -83,4 +83,19 @@ public class StudentImporter {
             throws Exception {
         return dataUploadService.uploadStudentRelate(workId, file.getInputStream());
     }
+
+    /**
+     * 导入缺考名单
+     * @param workId
+     * @param subject
+     * @param file
+     * @return
+     * @throws Exception
+     */
+    @RequestMapping(value = "/missingStudent", method = RequestMethod.POST)
+    public List<ExcelError> missingStudent(@RequestParam Long workId,
+                                     @RequestParam Subject subject,
+                                     @RequestParam MultipartFile file) throws Exception {
+        return dataUploadService.uploadMissingStudents(workId, subject, file.getInputStream());
+    }
 }

+ 88 - 13
stmms-ms-admin/src/main/java/cn/com/qmth/stmms/ms/admin/service/DataUploadService.java

@@ -1,6 +1,7 @@
 package cn.com.qmth.stmms.ms.admin.service;
 
 import cn.com.qmth.stmms.ms.admin.dto.StudentDTO;
+import cn.com.qmth.stmms.ms.admin.dto.StudentMissingDTO;
 import cn.com.qmth.stmms.ms.admin.dto.StudentRelateDTO;
 import cn.com.qmth.stmms.ms.commons.config.ImageCompressionConfig;
 import cn.com.qmth.stmms.ms.commons.config.ImageConfig;
@@ -20,13 +21,13 @@ import cn.com.qmth.stmms.ms.core.repository.*;
 import cn.com.qmth.stmms.ms.core.vo.Subject;
 import com.alibaba.fastjson.JSONObject;
 import org.apache.commons.codec.digest.DigestUtils;
-import org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.util.CollectionUtils;
 
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
@@ -241,6 +242,7 @@ public class DataUploadService {
             throw new Exception("已有采集数据,不能导入考生数据");
         }
         examQuestionRepo.deleteByWorkId(workId);
+        List<Student> studentList = new ArrayList<>();
 
         ExcelReader excelReader = new ExcelReader(StudentDTO.class);
         List<ExcelError> excelErrors = excelReader.reader(inputStream, new ExcelReaderHandle() {
@@ -282,7 +284,8 @@ public class DataUploadService {
                         if (imageConfig.isCustomSubject()) {
                             student.setUploadStatus("CUSTOM:0");
                         }
-                        studentRepo.save(student);
+//                        studentRepo.save(student);
+                        studentList.add(student);
                     } else if (student != null) {
                         if (isAbsent) {
                             student.setAbsent(isAbsent);
@@ -311,8 +314,12 @@ public class DataUploadService {
                             student.setExamRoom(dto.getExamRoom());
                             student.setSourceName(dto.getSourceName());
                             student.setSchool(dto.getSchool());
+//                            student.setScMissing(false);
+//                            student.setSxMissing(false);
+//                            student.setSmMissing(false);
                         }
-                        studentRepo.save(student);
+//                        studentRepo.save(student);
+                        studentList.add(student);
                     }
                     return null;
                 } catch (RuntimeException e) {
@@ -325,9 +332,24 @@ public class DataUploadService {
             }
         });
         String errors = errorsString(excelErrors);
-        if(errors.length() > 0) {
+        if (errors.length() > 0) {
             throw new Exception(errors);
         }
+
+        if (!CollectionUtils.isEmpty(studentList)) {
+            List<Student> data = new ArrayList<>();
+            for (Student stu : studentList) {
+                if (data.size() == 2000) {
+                    studentRepo.save(data);
+                    data.clear();
+                }
+                data.add(stu);
+            }
+            //将剩下的数据也导入
+            if (!data.isEmpty()) {
+                studentRepo.save(data);
+            }
+        }
         return excelErrors;
     }
 
@@ -347,7 +369,7 @@ public class DataUploadService {
             throw new Exception("没有此工作区,请检查workId是否正确");
         }
         List<Paper> papers = paperRepo.findByWorkIdAndSubject(workId, subject);
-        if(papers.isEmpty() || papers.size() == 0){
+        if (papers.isEmpty() || papers.size() == 0) {
             throw new Exception("没有采集数据,不能导入试评数据");
         }
         List<MarkSubject> markSubjectList = work.getSubjects().stream().filter(o -> o.getId().toUpperCase().contains(subject.toString().toUpperCase()))
@@ -370,6 +392,10 @@ public class DataUploadService {
         paperRepo.deleteByWorkIdAndSubjectAndTest(workId, subject, TrialEnum.INIT.getId());
         long startTime = System.currentTimeMillis();
         ExcelReader excelReader = new ExcelReader(StudentDTO.class);
+
+        List<Student> studentList = new ArrayList<>();
+        List<Paper> paperList = new ArrayList<>();
+
         List<ExcelError> excelErrors = excelReader.reader(inputStream, new ExcelReaderHandle() {
 
             @Override
@@ -429,7 +455,8 @@ public class DataUploadService {
                     BeanUtils.copyProperties(student, studentNew);
                     studentNew.setId(null);
                     studentNew.setTest(jsonObject.toJSONString());
-                    studentRepo.save(studentNew);
+//                    studentRepo.save(studentNew);
+                    studentList.add(studentNew);
 
                     Paper paperNew = new Paper();
                     BeanUtils.copyProperties(paper, paperNew);
@@ -447,7 +474,8 @@ public class DataUploadService {
                     paperNew.setSecretNumber(new StringBuffer(String.valueOf(subject.ordinal())).append(questionNew.getAreaCode()).append(random).toString());
                     paperNew.setActive(true);
                     paperNew.setBatchNo(startTime);
-                    paperRepo.save(paperNew);
+//                    paperRepo.save(paperNew);
+                    paperList.add(paperNew);
                 } catch (Exception e) {
                     ExcelError excelError = new ExcelError();
                     excelError.setExcelErrorType(e.getMessage());
@@ -456,11 +484,17 @@ public class DataUploadService {
                 return null;
             }
         });
+        if (!CollectionUtils.isEmpty(studentList)) {
+            studentRepo.save(studentList);
+        }
+        if (!CollectionUtils.isEmpty(paperList)) {
+            paperRepo.save(paperList);
+        }
         if (Objects.nonNull(excelErrors) && excelErrors.size() > 0) {
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
         }
         String errors = errorsString(excelErrors);
-        if(errors.length() > 0) {
+        if (errors.length() > 0) {
             throw new Exception(errors);
         }
         return excelErrors;
@@ -492,7 +526,7 @@ public class DataUploadService {
         paper.setUploadedOn(new Date());
         paper.setManual(isManual);
 
-        if(Objects.nonNull(level) && level != ""){
+        if (Objects.nonNull(level) && level != "") {
             paper.setLevel(level);
             //设置为已发布任务的状态
             paper.setActive(true);
@@ -500,7 +534,7 @@ public class DataUploadService {
             paper.setBatchNo(1l);
         }
 
-        if(!imageServerConfig.isAliyunOss()) {
+        if (!imageServerConfig.isAliyunOss()) {
             String sheetPath = null;
             String slicePath = null;
             String fileName = student.getExamNumber();
@@ -593,7 +627,7 @@ public class DataUploadService {
             }
         });
         String errors = errorsString(excelErrors);
-        if(errors.length() > 0) {
+        if (errors.length() > 0) {
             throw new Exception(errors);
         }
         return excelErrors;
@@ -653,13 +687,14 @@ public class DataUploadService {
 
     /**
      * 拼接导入异常信息
+     *
      * @param excelErrors
      * @return
      */
     private String errorsString(List<ExcelError> excelErrors) {
         StringJoiner sj = new StringJoiner(";");
-        if(!excelErrors.isEmpty() && excelErrors.size() > 0){
-            int forint = excelErrors.size() < 10 ? excelErrors.size() : 9 ;
+        if (!excelErrors.isEmpty() && excelErrors.size() > 0) {
+            int forint = excelErrors.size() < 10 ? excelErrors.size() : 9;
             for (int i = 0; i < forint; i++) {
                 ExcelError excelError = excelErrors.get(i);
                 StringBuffer sb = new StringBuffer();
@@ -669,4 +704,44 @@ public class DataUploadService {
         }
         return sj.toString();
     }
+
+    @Transactional
+    public List<ExcelError> uploadMissingStudents(Long workId, Subject subject, InputStream inputStream) throws Exception {
+        List<Student> studentList = studentRepo.findByWorkIdAndTest(workId, String.valueOf(TrialEnum.DEFAULT.getId()));
+        if (CollectionUtils.isEmpty(studentList)) {
+            throw new Exception("请先导入考生数据");
+        }
+        ExcelReader excelReader = new ExcelReader(StudentMissingDTO.class);
+        List<ExcelError> excelErrors = excelReader.reader(inputStream, obj -> {
+            try {
+                StudentMissingDTO dto = (StudentMissingDTO) obj;
+                //校验excel文件中数据是否完整
+                Student student = studentRepo.findByWorkIdAndExamNumberAndTest(workId, dto.getExamNumber(), String.valueOf(TrialEnum.DEFAULT.getId()));
+                if (student == null) {
+                    throw new RuntimeException("考生不存在");
+                }
+                //设置缺考
+                if (Subject.SC.equals(subject)) {
+                    student.setScMissing(true);
+                } else if (Subject.SX.equals(subject)) {
+                    student.setSxMissing(true);
+                } else if (Subject.SM.equals(subject)) {
+                    student.setSmMissing(true);
+                }
+                studentRepo.save(student);
+                return null;
+            } catch (RuntimeException e) {
+                //手动回滚
+                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+                ExcelError excelError = new ExcelError();
+                excelError.setExcelErrorType(e.getMessage());
+                return excelError;
+            }
+        });
+        String errors = errorsString(excelErrors);
+        if (errors.length() > 0) {
+            throw new Exception(errors);
+        }
+        return excelErrors;
+    }
 }

+ 25 - 3
stmms-ms-collect/src/main/java/cn/com/qmth/stmms/ms/collect/api/CollectApi.java

@@ -287,6 +287,7 @@ public class CollectApi {
             }
         }
         dto.setUploadTime(sdf.format(new Date()));
+        paper.setRelate(!StringUtils.equals(student.getExamNumber(), student.getRelateExamNumber()));
         if (paper != null) {
             dto.setPaperId(paper.getId());
         }
@@ -1201,7 +1202,9 @@ public class CollectApi {
     public List<Map<String, Object>> getExportData(@RequestParam Long workId,
                                                    @RequestParam String imageType,
                                                    @RequestParam(required = false) Integer startScore,
-                                                   @RequestParam(required = false) Integer endScore) {
+                                                   @RequestParam(required = false) Integer endScore,
+                                                   @RequestParam(required = false) String scores,
+                                                   @RequestParam(required = false) Integer countPerScore) {
         StringBuffer sql = new StringBuffer();
         sql.append("SELECT s.id studentId,s.name studentName,s.school, p.work_id workId, p.subject, p.area_code areaCode,s.area_name areaName, p.exam_number examNumber,ms.name subjectName, 0+cast(p.score as char) as score FROM paper p LEFT JOIN student s ON p.work_id = s.work_id AND p.exam_number = s.exam_number left join mark_subject ms on concat(p.work_id,'-', p.subject) = ms.id WHERE p.score is not null and p.work_id = ").append(workId);
         if (startScore != null && endScore != null) {
@@ -1214,6 +1217,9 @@ public class CollectApi {
         if (startScore == null && endScore != null) {
             sql.append(" and p.score = ").append(endScore);
         }
+        if (StringUtils.isNotBlank(scores)) {
+            sql.append(" and p.score in ( ").append(scores).append(")");
+        }
         List<Map<String, Object>> paperList = jdbcTemplate.queryForList(sql.toString());
         //图片后缀
         String suffix = ".jpg";
@@ -1227,8 +1233,24 @@ public class CollectApi {
         } else {
             throw new RuntimeException("图片类型有误");
         }
+        List<Map<String, Object>> finalList = new ArrayList<>();
         if (paperList != null && paperList.size() > 0) {
-            paperList.stream().map(m -> {
+            Map<String, List<Map<String, Object>>> listMap = paperList.stream().collect(Collectors.groupingBy(m -> m.get("score").toString()));
+            for (Map.Entry<String, List<Map<String, Object>>> listEntry : listMap.entrySet()) {
+                List<Map<String, Object>> entryValue = listEntry.getValue();
+                if (countPerScore == null) {
+                    finalList.addAll(entryValue);
+                } else {
+                    int count = countPerScore.intValue();
+                    if(entryValue.size() <= count){
+                        finalList.addAll(entryValue);
+                    } else {
+                        finalList.addAll(entryValue.subList(0, count));
+                    }
+                }
+            }
+
+            finalList.stream().map(m -> {
                 Long studentId = Long.valueOf(m.get("studentId").toString());
                 String subject = m.get("subject").toString();
                 Subject subject1 = Subject.valueOf(subject);
@@ -1254,7 +1276,7 @@ public class CollectApi {
             }).collect(Collectors.toList());
 
         }
-        return paperList;
+        return finalList;
     }
 
     //客户端图片导出,单个导出

+ 58 - 0
stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/config/DbConfig.java

@@ -0,0 +1,58 @@
+package cn.com.qmth.stmms.ms.commons.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @Description: 数据库参数
+ */
+@Configuration
+@ConfigurationProperties(prefix = "db", ignoreUnknownFields = false)
+public class DbConfig {
+
+    private String host;
+    private String port;
+    private String dbName;
+    private String userName;
+    private String password;
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(String host) {
+        this.host = host;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(String port) {
+        this.port = port;
+    }
+
+    public String getDbName() {
+        return dbName;
+    }
+
+    public void setDbName(String dbName) {
+        this.dbName = dbName;
+    }
+
+    public String getUserName() {
+        return userName;
+    }
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+}

+ 22 - 8
stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/utils/AesUtil.java

@@ -2,6 +2,7 @@ package cn.com.qmth.stmms.ms.commons.utils;
 
 import cn.com.qmth.stmms.ms.commons.constant.SystemConstant;
 import org.slf4j.LoggerFactory;
+import org.springframework.util.StringUtils;
 
 import javax.crypto.Cipher;
 import javax.crypto.spec.IvParameterSpec;
@@ -18,6 +19,8 @@ import java.util.Base64;
 public class AesUtil {
     private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AesUtil.class);
 
+    private static final String RULE = "123456790abcdefg";
+
     /**
      * 加密
      *
@@ -74,14 +77,16 @@ public class AesUtil {
      *
      * @param content
      * @param encryptKey
-     * @param rule
      * @return
      * @throws Exception
      */
     public static String encoder(String content, String encryptKey, String rule) throws Exception {
-        LOGGER.info("AES加密前的内容:{},key:{}", content, encryptKey);
-        String encoderText = Base64.getEncoder().encodeToString(encrypt(content, encryptKey, rule));
-        LOGGER.info("AES加密后的文本:{}", encoderText);
+        if(StringUtils.isEmpty(rule)){
+            rule = RULE;
+        }
+//        LOGGER.info("AES加密前的内容:{},key:{}", content, encryptKey);
+        String encoderText = Base64.getEncoder().encodeToString(encrypt(content, encryptKey, RULE));
+//        LOGGER.info("AES加密后的文本:{}", encoderText);
         return encoderText;
     }
 
@@ -90,14 +95,23 @@ public class AesUtil {
      *
      * @param encryptStr
      * @param decryptKey
-     * @param rule
      * @return
      * @throws Exception
      */
     public static String decoder(String encryptStr, String decryptKey, String rule) throws Exception {
-        LOGGER.info("AES解密前的内容:{},key:{}", encryptStr, decryptKey);
-        String decoderText = decrypt(Base64.getDecoder().decode(encryptStr), decryptKey, rule);
-        LOGGER.info("AES解密后的文本:{}", decoderText);
+        if(StringUtils.isEmpty(rule)){
+            rule = RULE;
+        }
+//        LOGGER.info("AES解密前的内容:{},key:{}", encryptStr, decryptKey);
+        String decoderText = decrypt(Base64.getDecoder().decode(encryptStr), decryptKey, RULE);
+//        LOGGER.info("AES解密后的文本:{}", decoderText);
         return decoderText;
     }
+
+    public static void main(String[] args) throws Exception {
+        String encoder = encoder("1622615449555#admin#0", "token", null);
+        System.out.println(encoder);
+        String decoder = decoder(encoder, "token", null);
+        System.out.println(decoder);
+    }
 }

+ 63 - 0
stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/utils/DbBackupUtils.java

@@ -0,0 +1,63 @@
+package cn.com.qmth.stmms.ms.commons.utils;
+
+import cn.com.qmth.stmms.ms.commons.config.DbConfig;
+import org.apache.http.client.utils.DateUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @Date: 2021/5/31.
+ */
+@Component
+public class DbBackupUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DbBackupUtils.class);
+
+    ExecutorService service = Executors.newFixedThreadPool(1);
+
+    @Autowired
+    DbConfig dbConfig;
+
+    public void startBackup() {
+        service.submit(dbBackUp());
+    }
+
+    public Runnable dbBackUp() {
+        return () -> {
+            String pathSql = dbConfig.getDbName() + DateUtils.formatDate(new Date(), "yyyyMMddHHmmss") + ".sql";
+            File fileSql = new File(pathSql);
+            try {
+                //创建备份sql文件
+                if (!fileSql.exists()) {
+                    fileSql.createNewFile();
+                }
+                //mysqldump -hlocalhost -uroot -p123456 db > /home/back.sql
+                StringBuffer sb = new StringBuffer();
+                sb.append("mysqldump");
+                sb.append(" -h " + dbConfig.getHost());
+                sb.append(" -u" + dbConfig.getUserName());
+                sb.append(" -p" + dbConfig.getPassword());
+                sb.append(" " + dbConfig.getDbName() + " >");
+                sb.append(pathSql);
+                LOGGER.info("cmd命令为:{},开始备份数据库:" + sb.toString(), dbConfig.getDbName());
+                Runtime runtime = Runtime.getRuntime();
+                Process process = runtime.exec("cmd /c" + sb.toString());
+                LOGGER.info("备份成功!");
+            } catch (IOException e) {
+                LOGGER.info("备份失败!原因:{}", e.getMessage());
+            }
+        };
+    }
+
+    public static void main(String[] args) {
+        System.out.println(System.getProperty("os.name"));
+    }
+}

+ 40 - 0
stmms-ms-commons/src/main/java/cn/com/qmth/stmms/ms/commons/utils/ServletUtil.java

@@ -0,0 +1,40 @@
+package cn.com.qmth.stmms.ms.commons.utils;
+
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * @Date: 2021/5/31.
+ */
+public class ServletUtil {
+
+    /**
+     * 获取HttpServletRequest
+     *
+     * @return
+     */
+    public static HttpServletRequest getRequest() {
+        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        return servletRequestAttributes.getRequest();
+    }
+
+    /**
+     * 获取用户ID
+     * @return
+     */
+    public static String getUserId() {
+        HttpServletRequest request = getRequest();
+        return request.getHeader("userId");
+    }
+
+    /**
+     * 获取工作ID
+     * @return
+     */
+    public static String getWordId() {
+        HttpServletRequest request = getRequest();
+        return request.getHeader("wordId");
+    }
+}

+ 13 - 0
stmms-ms-core/pom.xml

@@ -25,5 +25,18 @@
             <groupId>cn.com.qmth</groupId>
             <artifactId>stmms-ms-commons</artifactId>
         </dependency>
+
+        <!--开启 cache 缓存 -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
+        <!-- ehcache缓存 -->
+        <dependency>
+            <groupId>net.sf.ehcache</groupId>
+            <artifactId>ehcache</artifactId>
+            <version>2.10.6</version><!--$NO-MVN-MAN-VER$ -->
+        </dependency>
     </dependencies>
 </project>

+ 54 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/cache/CacheService.java

@@ -0,0 +1,54 @@
+package cn.com.qmth.stmms.ms.core.cache;
+
+import cn.com.qmth.stmms.ms.core.repository.MarkUserRepo;
+import cn.com.qmth.stmms.ms.core.vo.Subject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Date: 2021/6/1.
+ */
+@Service
+public class CacheService {
+
+    @Autowired
+    MarkUserRepo markUserRepo;
+
+    //这里的单引号不能少,否则会报错,被识别是一个对象
+    private static final String CACHE_NAME = "tokens";
+
+    /**
+     * 保存
+     * @param userId
+     * @return
+     */
+    @CachePut(value = CACHE_NAME, key = "'token' + #userId")
+    public String saveTokenCache(String userId, String token) {
+        return token;
+    }
+
+    /**
+     * 删除
+     * @param userId
+     * @return
+     */
+    @CacheEvict(value = CACHE_NAME, key = "'token' + #userId")
+    public String deleteTokenCache(String userId) {
+        return null;
+    }
+
+
+    /**
+     * 查询
+     * @param userId
+     * @return
+     */
+    @Cacheable(value=CACHE_NAME,key="'token'+ #userId")
+    public String queryTokenCache(String userId){
+        return null;
+    }
+
+}

+ 10 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/Paper.java

@@ -142,6 +142,8 @@ public class Paper implements Serializable {
 
     private Boolean isRejectedByLeader;
 
+    private boolean isRelate;
+
     public int getTest() {
         return test;
     }
@@ -536,4 +538,12 @@ public class Paper implements Serializable {
     public void setRejectedByLeader(Boolean rejectedByLeader) {
         isRejectedByLeader = rejectedByLeader;
     }
+
+    public boolean isRelate() {
+        return isRelate;
+    }
+
+    public void setRelate(boolean relate) {
+        isRelate = relate;
+    }
 }

+ 44 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/ParamSetting.java

@@ -46,6 +46,14 @@ public class ParamSetting implements Serializable {
 
     private Integer isLevelToScore;
 
+    private Integer propDenominator;
+
+    private Integer autoCallbackShowDeviation;
+
+    private Integer showSample;
+
+    private Integer directScore;
+
     public static ParamSetting init(){
         ParamSetting paramSetting = new ParamSetting();
         paramSetting.setPackageScan(0);
@@ -64,6 +72,10 @@ public class ParamSetting implements Serializable {
         paramSetting.setScoreShowAllPaper(0);
         //是否谁分档谁打分,默认0(否)
         paramSetting.setIsLevelToScore(0);
+        paramSetting.setPropDenominator(1);
+        paramSetting.setAutoCallbackShowDeviation(1);
+        paramSetting.setShowSample(1);
+        paramSetting.setDirectScore(0);
         return paramSetting;
     }
 
@@ -194,4 +206,36 @@ public class ParamSetting implements Serializable {
     public void setIsLevelToScore(Integer isLevelToScore) {
         this.isLevelToScore = isLevelToScore;
     }
+
+    public Integer getPropDenominator() {
+        return propDenominator;
+    }
+
+    public void setPropDenominator(Integer propDenominator) {
+        this.propDenominator = propDenominator;
+    }
+
+    public Integer getAutoCallbackShowDeviation() {
+        return autoCallbackShowDeviation;
+    }
+
+    public void setAutoCallbackShowDeviation(Integer autoCallbackShowDeviation) {
+        this.autoCallbackShowDeviation = autoCallbackShowDeviation;
+    }
+
+    public Integer getShowSample() {
+        return showSample;
+    }
+
+    public void setShowSample(Integer showSample) {
+        this.showSample = showSample;
+    }
+
+    public Integer getDirectScore() {
+        return directScore;
+    }
+
+    public void setDirectScore(Integer directScore) {
+        this.directScore = directScore;
+    }
 }

+ 33 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/Student.java

@@ -55,6 +55,12 @@ public class Student implements Serializable {
 
     private String school;
 
+    private boolean scMissing;
+
+    private boolean sxMissing;
+
+    private boolean smMissing;
+
     public static long getSerialVersionUID() {
         return serialVersionUID;
     }
@@ -78,6 +84,9 @@ public class Student implements Serializable {
         this.test = test;
         this.relateExamNumber = examNumber;
         this.school = school;
+        this.scMissing = false;
+        this.sxMissing = false;
+        this.smMissing = false;
     }
 
     public Student() {
@@ -178,4 +187,28 @@ public class Student implements Serializable {
     public void setSchool(String school) {
         this.school = school;
     }
+
+    public boolean isScMissing() {
+        return scMissing;
+    }
+
+    public void setScMissing(boolean scMissing) {
+        this.scMissing = scMissing;
+    }
+
+    public boolean isSxMissing() {
+        return sxMissing;
+    }
+
+    public void setSxMissing(boolean sxMissing) {
+        this.sxMissing = sxMissing;
+    }
+
+    public boolean isSmMissing() {
+        return smMissing;
+    }
+
+    public void setSmMissing(boolean smMissing) {
+        this.smMissing = smMissing;
+    }
 }

+ 10 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/task/MarkTask.java

@@ -99,6 +99,8 @@ public class MarkTask implements Serializable {
 
     private Integer manualScore;
 
+    private String deviationDirection;
+
     public Long getBatchNo() {
         return batchNo;
     }
@@ -375,4 +377,12 @@ public class MarkTask implements Serializable {
     public void setSample(boolean sample) {
         isSample = sample;
     }
+
+    public String getDeviationDirection() {
+        return deviationDirection;
+    }
+
+    public void setDeviationDirection(String deviationDirection) {
+        this.deviationDirection = deviationDirection;
+    }
 }

+ 10 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/domain/user/MarkUser.java

@@ -50,6 +50,8 @@ public class MarkUser extends AbstractUser {
 
     private boolean levelCallback;
 
+    private boolean leaderConfirm;
+
     private Integer displayCount;
 
     private Boolean defaultAccount;
@@ -65,6 +67,14 @@ public class MarkUser extends AbstractUser {
         this.levelCallback = levelCallback;
     }
 
+    public boolean getLeaderConfirm() {
+        return leaderConfirm;
+    }
+
+    public void setLeaderConfirm(boolean leaderConfirm) {
+        this.leaderConfirm = leaderConfirm;
+    }
+
     public static long getSerialVersionUID() {
         return serialVersionUID;
     }

+ 5 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/MarkTaskRepo.java

@@ -254,4 +254,9 @@ public interface MarkTaskRepo extends JpaRepository<MarkTask, Long>, JpaSpecific
     @Modifying
     @Query(value = "update mark_task m set m.level = ?1 where m.paper_id = ?2", nativeQuery = true)
     void updateLevelByPaperId(String level, Long id);
+
+    @Query(value = "select count(1) from mark_task m inner join paper p on m.paper_id = p.id and p.score_batch_no = ?4 where m.question_id = ?1 and m.marker_id = ?2 and m.stage = ?3 and m.manual_score = 1", nativeQuery = true)
+    int countByQuestionIdAndMarkerIdAndStageAndManualScore(Long questionId, Long markerId, int stage, Long batchNo);
+
+    List<MarkTask> findByWorkIdAndQuestionIdAndStageAndManualScore(Long workId, Long questionId, MarkStage score, int i);
 }

+ 2 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/MarkUserRepo.java

@@ -33,4 +33,6 @@ public interface MarkUserRepo extends JpaRepository<MarkUser,Long>, JpaSpecifica
     MarkUser findByPassword(String string);
 
     List<MarkUser> findByOrganizationIdAndRoleIn(Long organizationId, Role... role);
+
+    List<MarkUser> findByWorkIdAndSubject(Long workId, Subject subject);
 }

+ 3 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/PaperRepo.java

@@ -385,5 +385,8 @@ public interface PaperRepo extends JpaRepository<Paper, Long>, JpaSpecificationE
 
     List<Paper> findByworkIdAndSubjectAndQuestionIdAndIsMissingAndTestAndLevelIsNull(Long workId, Subject valueOf, Long questionId, boolean b, int ordinal);
 
+    @Query(value = "select count(distinct p.id) from mark_task m inner join paper p on m.paper_id = p.id and m.stage = ?3 where p.work_id = ?1 and p.subject = ?2 and m.manual_score = 1", nativeQuery = true)
+    int countByWorkIdAndSubjectAndStageAndManualScore(Long workId, String subject, int stage);
+
 //    List<Paper> findByWorkIdAndSubjectAndInspectRange(Long workId, Subject subject, Long inspectRange);
 }

+ 7 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/repository/StudentRepo.java

@@ -1,6 +1,7 @@
 package cn.com.qmth.stmms.ms.core.repository;
 
 import cn.com.qmth.stmms.ms.core.domain.Student;
+import cn.com.qmth.stmms.ms.core.vo.Subject;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Query;
@@ -37,4 +38,10 @@ public interface StudentRepo extends JpaRepository<Student, Long>, JpaSpecificat
     List<Student> findByWorkId(Long workId);
 
     Student findByWorkIdAndAreaCodeAndExamNumber(Long workId, String areaCode, String examNumber);
+
+    List<Student> findByWorkIdAndTestAndScMissingIsTrue(Long workId, String valueOf);
+
+    List<Student> findByWorkIdAndTestAndSxMissingIsTrue(Long workId, String valueOf);
+
+    List<Student> findByWorkIdAndTestAndSmMissingIsTrue(Long workId, String valueOf);
 }

+ 8 - 0
stmms-ms-core/src/main/java/cn/com/qmth/stmms/ms/core/specification/StudentSpecification.java

@@ -3,6 +3,7 @@ package cn.com.qmth.stmms.ms.core.specification;
 import cn.com.qmth.stmms.ms.commons.utils.specification.PagingAndSortingSpecification;
 import cn.com.qmth.stmms.ms.core.domain.Student;
 import cn.com.qmth.stmms.ms.core.domain.enums.TrialEnum;
+import cn.com.qmth.stmms.ms.core.vo.Subject;
 import org.springframework.data.jpa.domain.Specification;
 
 import javax.persistence.criteria.Predicate;
@@ -163,6 +164,13 @@ public class StudentSpecification extends PagingAndSortingSpecification {
             if (getUpload() != null && !getUpload()) {
                 if (Objects.nonNull(getSubject()) && !getSubject().isEmpty()) {
                     predicates.add(cb.like(root.get("uploadStatus"), "%" + getSubject() + ":0" + "%"));
+                    if(getSubject().equals(Subject.SC.name())){
+                        predicates.add(cb.equal(root.get("scMissing"), true));
+                    } else if(getSubject().equals(Subject.SX.name())){
+                        predicates.add(cb.equal(root.get("sxMissing"), true));
+                    } else if(getSubject().equals(Subject.SM.name())){
+                        predicates.add(cb.equal(root.get("smMissing"), true));
+                    }
                 } else {
                     predicates.add(cb.equal(root.get("uploadStatus"), "SX:0,SC:0,SM:0"));
                 }

+ 33 - 2
stmms-ms-log/src/main/java/cn/com/qmth/stmms/ms/log/aop/MarkLogAop.java

@@ -3,6 +3,7 @@ package cn.com.qmth.stmms.ms.log.aop;
 import cn.com.qmth.stmms.ms.commons.config.LevelConfig;
 import cn.com.qmth.stmms.ms.commons.constant.ArbitrateCallback;
 import cn.com.qmth.stmms.ms.commons.constant.ArbitrateResult;
+import cn.com.qmth.stmms.ms.commons.utils.AesUtil;
 import cn.com.qmth.stmms.ms.commons.utils.SqlUtil;
 import cn.com.qmth.stmms.ms.core.cache.ParamCache;
 import cn.com.qmth.stmms.ms.core.domain.*;
@@ -14,6 +15,7 @@ import cn.com.qmth.stmms.ms.marking.config.MarkingConfig;
 import cn.com.qmth.stmms.ms.marking.dto.MarkTaskDTO;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import org.apache.commons.lang.StringUtils;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.annotation.AfterReturning;
 import org.aspectj.lang.annotation.Aspect;
@@ -90,6 +92,9 @@ public class MarkLogAop {
     @Resource
     MarkTaskJobRepo markTaskJobRepo;
 
+    @Autowired
+    LevelRepo levelRepo;
+
     /**
      * 标准卷、定档、档位打回切入点
      */
@@ -283,6 +288,10 @@ public class MarkLogAop {
             markTaskJobRepo.updateMarkTaskJobByVersion(markTaskJob.getId(), markTaskJob.getVersion());
             return;
         }
+
+        List<Level> levels = levelRepo.findByWorkId(paper.getWorkId());
+        Map<String, Integer> levelMap = levels.stream().collect(Collectors.toMap(Level::getCode, Level::getLevelValue));
+
         Map<Long, String> levelsMap = markTasks.stream().collect(Collectors.toMap(MarkTask::getMarkerId, o -> o.getResult().toUpperCase()));
         LOGGER.info("this:{} markLogAop异步回调进来了", this);
         arbitrateCallback.judge(levelsMap, deviation, new ArbitrateResult() {
@@ -298,6 +307,7 @@ public class MarkLogAop {
                     for (ArbitrateCallback.Distance d : list) {
                         for (MarkTask m : markTasks) {
                             if (Objects.equals(String.valueOf(d.getC()), m.getResult()) && d.getMarkId().longValue() == m.getMarkerId().longValue()) {
+                                m.setDeviationDirection(calcDeviationDirection(levelMap, m.getResult(), m.getLevel()));
                                 m.setRejected(true);
                                 m.setOriginLevel(m.getResult());
                                 m.setResult(null);
@@ -325,6 +335,25 @@ public class MarkLogAop {
         LOGGER.info("this:{} markLogAop异步回调结束了", this);
     }
 
+    /**
+     * 计算偏差方向
+     *
+     * @param levelMap
+     * @param result   定档档位
+     * @param level    评卷员打的档位
+     * @return
+     */
+    private String calcDeviationDirection(Map<String, Integer> levelMap, String result, String level) {
+        if (StringUtils.isNotBlank(result) && StringUtils.isNotBlank(level)) {
+            Integer resultInt = levelMap.get(result);
+            Integer levelInt = levelMap.get(level);
+            if (resultInt != null && levelInt != null) {
+                return String.valueOf(levelInt.intValue() - levelInt.intValue());
+            }
+        }
+        return null;
+    }
+
     /**
      * 查询处于哪个档位公用
      *
@@ -369,10 +398,12 @@ public class MarkLogAop {
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
         HttpServletRequest request = attributes.getRequest();
         HttpSession session = request.getSession();
-        String token = (String) session.getAttribute(TOKEN);
+//        String token = (String) session.getAttribute(TOKEN);
+        String token = request.getHeader("Authorization");
         if (Objects.isNull(token)) {
             throw new Exception("请先登录");
         }
+        token = AesUtil.decoder(token, "token", null);
         Object[] args = joinPoint.getArgs(); // 参数值
         MarkTask markTask = null;
         MarkUser markUser = null;
@@ -389,7 +420,7 @@ public class MarkLogAop {
         } else {
             String userId = request.getHeader("userId");
             markUser = markUserRepo.findOne(Long.valueOf(userId));
-            if(Objects.isNull(markUser)) {
+            if (Objects.isNull(markUser)) {
                 String[] strs = token.split("#");
                 markUser = strs == null || strs[2] == null || strs[2] == "null" ? null : markUserRepo.findOne(Long.parseLong(strs[2]));
             }

+ 2 - 0
stmms-ms-main/src/main/java/cn/com/qmth/stmms/ms/Application.java

@@ -3,6 +3,7 @@ package cn.com.qmth.stmms.ms;
 
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
@@ -23,6 +24,7 @@ import java.io.File;
 @EnableTransactionManagement
 @EnableAsync
 @EnableScheduling
+@EnableCaching
 public class Application {
 
     public static void main(String[] args) throws Exception {

+ 11 - 5
stmms-ms-main/src/main/resources/application-dev.properties

@@ -2,9 +2,15 @@
 #spring.datasource.username=root
 #spring.datasource.password=root
 
-spring.datasource.url=jdbc:mysql://localhost:3306/chuanyin-2020-v2.0?useUnicode=true&characterEncoding=UTF-8
-spring.datasource.username=root
-spring.datasource.password=root
+db.host=localhost
+db.port=3306
+db.dbName=msyj-org-2.0.0
+db.userName=root
+db.password=root
+
+spring.datasource.url=jdbc:mysql://${db.host}:${db.port}/${db.dbName}?useUnicode=true&characterEncoding=UTF-8
+spring.datasource.username=${db.userName}
+spring.datasource.password=${db.password}
 
 spring.datasource.validation-query=SELECT 1 FROM DUAL
 spring.datasource.test-on-borrow=true
@@ -29,7 +35,7 @@ spring.jpa.hibernate.ddl-auto=update
 
 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
 spring.jackson.time-zone=GMT+8
-
+spring.cache.ehcache.config=classpath:ehcache.xml
 
 spring.http.multipart.max-file-size=10Mb
 app.config.roundUp=true
@@ -93,7 +99,7 @@ score.level.taskSort=paper
 #score.level.taskSort=paper
 
 #\u524D\u7AEF\u9759\u6001\u8D44\u6E90\u76EE\u5F55
-web.upload-path=D:\\company\\qmth\\bat-pro\\msyj-202009-v2.0\\static\\
+web.upload-path=D:\\company\\qmth\\bat-pro\\msyj-2021-v2.0.0\\dist\\
 spring.mvc.static-path-pattern=/**
 spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/,file:${sys.config.localhostPath},file:${web.upload-path}
 

+ 1 - 1
stmms-ms-main/src/main/resources/application.properties

@@ -1,4 +1,4 @@
-#spring.profiles.active=dev
+spring.profiles.active=dev
 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/stmms-ms-2?useUnicode=true&characterEncoding=UTF-8
 spring.datasource.username=root
 spring.datasource.password=root

+ 45 - 0
stmms-ms-main/src/main/resources/ehcache.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
+         updateCheck="false">
+
+    <!-- diskStore:ehcache其实是支持内存+磁盘+堆外内存,几个层级的缓存 -->
+    <!-- 在这里设置一下,但是一般不用的 -->
+    <diskStore path="java.io.tmpdir/Tmp_EhCache" />
+
+    <!-- defaultCache,是默认的缓存策略 -->
+    <!-- 如果你指定的缓存策略没有找到,那么就用这个默认的缓存策略 -->
+    <!-- external:如果设置为true的话,那么timeout就没有效果,缓存就会一直存在,一般默认就是false -->
+    <!-- maxElementsInMemory:内存中可以缓存多少个缓存条目,在实践中,你是需要自己去计算的,比如你计算你要缓存的对象是什么?有多大?最多可以缓存多少MB,或者多少个G的数据?除以每个对象的大小,计算出最多可以放多少个对象 -->
+    <!-- overflowToDisk:如果内存不够的时候,是否溢出到磁盘 -->
+    <!-- diskPersistent:是否启用磁盘持久化的机制,在jvm崩溃的时候和重启之间,不用 -->
+    <!-- timeToIdleSeconds:对象最大的闲置的时间,如果超出闲置的时间,可能就会过期,我们这里就不用了,缓存最多闲置5分钟就被干掉了 -->
+    <!-- timeToLiveSeconds:对象最多存活的时间,我们这里也不用,超过这个时间,缓存就过期,就没了 -->
+    <!-- memoryStoreEvictionPolicy:当缓存数量达到了最大的指定条目数的时候,需要采用一定的算法,从缓存中清除一批数据,LRU,最近最少使用算法,最近一段时间内,最少使用的那些数据,就被干掉了 -->
+    <defaultCache
+            eternal="false"
+            maxElementsInMemory="1000"
+            overflowToDisk="false"
+            diskPersistent="false"
+            timeToIdleSeconds="300"
+            timeToLiveSeconds="0"
+            memoryStoreEvictionPolicy="LRU" />
+
+    <!-- 手动指定的缓存策略 -->
+    <!-- 比如你一个应用吧,可能要缓存很多种不同的数据,比如说商品信息,或者是其他的一些数据 -->
+    <!-- 对不同的数据,缓存策略可以在这里配置多种 -->
+    <cache
+            name="tokens"
+            eternal="false"
+            maxElementsInMemory="1000"
+            overflowToDisk="false"
+            diskPersistent="false"
+            timeToIdleSeconds="0"
+            timeToLiveSeconds="0"
+            memoryStoreEvictionPolicy="LRU" />
+
+    <!-- ehcache这种东西,简单实用,是很快速的,1小时上手可以用在项目里了,没什么难度的 -->
+    <!-- ehcache这个技术,如果讲深了,里面的东西还是很多的,高级的feature,但是我们这里就不涉及了 -->
+
+</ehcache>
+

+ 39 - 0
stmms-ms-main/src/test/CacheTest.java

@@ -0,0 +1,39 @@
+
+
+import cn.com.qmth.stmms.ms.Application;
+import cn.com.qmth.stmms.ms.core.cache.CacheService;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @Date: 2021/5/27.
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = {Application.class})
+public class CacheTest {
+
+    @Autowired
+    CacheService cacheService;
+
+    @Test
+    public void test() {
+
+        String token1 = cacheService.queryTokenCache("68");
+        System.out.println("token1:" + token1);
+
+        String token2 = cacheService.saveTokenCache("68", "sc1");
+        System.out.println("token2:" + token2);
+
+        String token3 = cacheService.queryTokenCache("68");
+        System.out.println("token3:" + token3);
+
+        String token4 = cacheService.deleteTokenCache("68");
+        System.out.println("token4:" + token4);
+
+        String token5 = cacheService.queryTokenCache("68");
+        System.out.println("token5:" + token5);
+    }
+}

+ 7 - 0
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/MakrerApi.java

@@ -530,6 +530,13 @@ public class MakrerApi {
                 o.setCount(count);
             }
         });
+
+        // 输分试卷
+        LevelStatDTO manualScoreDTO = new LevelStatDTO();
+        manualScoreDTO.setId("manualScore");
+        int manualCount = markTaskRepo.countByQuestionIdAndMarkerIdAndStageAndManualScore(questionId, marker.getId(), MarkStage.SCORE.ordinal(), batchNo);
+        manualScoreDTO.setCount(manualCount);
+        levelStatDTOs.add(manualScoreDTO);
         return levelStatDTOs;
     }
 

+ 8 - 0
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/MarkSubjectApi.java

@@ -212,6 +212,14 @@ public class MarkSubjectApi {
                 o.setGcount(gcount);
             }
         });
+
+        // 输分试卷
+        LevelStatDTO manualScoreDTO = new LevelStatDTO();
+        manualScoreDTO.setId("manualScore");
+        int manualCount = paperRepo.countByWorkIdAndSubjectAndStageAndManualScore(markSubject.getWorkId(), markSubject.getSubject().name(), MarkStage.SCORE.ordinal());
+        manualScoreDTO.setCount(manualCount);
+        levelStatDTOs.add(manualScoreDTO);
+
         Collections.sort(levelStatDTOs, (o1, o2) -> {
             if (o1.getId() == null || o2.getId() == null) {
                 return 1;

+ 32 - 1
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/MarkTaskApi.java

@@ -116,7 +116,9 @@ public class MarkTaskApi {
             List<Object> batchNos = paperRepo.findScoreBatchNoByWorkIdAndSubject(workId, markUser.getSubject().name());
             if(batchNos !=null && batchNos.size() > 0){
                 Object object = batchNos.get(0);
-                scoreBatchNo = Long.valueOf(object.toString());
+                if(Objects.nonNull(object)) {
+                    scoreBatchNo = Long.valueOf(object.toString());
+                }
             }
         }
         Long finalBatchNo = batchNo;
@@ -251,6 +253,35 @@ public class MarkTaskApi {
         return new PageableDTO(markTaskDTOs, markTasks.getTotalElements(), markTasks.getTotalPages(), pageable.getPageNumber());
     }
 
+    /**
+     * 评卷员的输分试卷
+     *
+     * @param markerId   评卷员用户id
+     * @param questionId 试题id
+     * @return
+     */
+    @RequestMapping(value = "/manualScore", method = RequestMethod.GET)
+    public PageableDTO listManualScore(@RequestParam Long markerId,
+                            @RequestParam(required = false) Long workId,
+                            @RequestParam Long questionId,
+                            Pageable pageable) {
+        List<MarkTaskDTO> markTaskDTOs = new ArrayList<>();
+        Specification<MarkTask> specification = (root, query, builder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(builder.equal(root.get("workId"), workId));
+            predicates.add(builder.equal(root.get("questionId"), questionId));
+            predicates.add(builder.equal(root.get("markerId"), markerId));
+            predicates.add(builder.equal(root.get("manualScore"), 1)); // 1:输分
+            return builder.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+        Sort sort = new Sort("paper.level", "serialNumber", "randomSeq");
+        Pageable pageable1 = new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), sort);
+        Page<MarkTask> markTasks = markTaskRepo.findAll(specification, pageable1);
+
+        markTasks.getContent().forEach(m -> markTaskDTOs.add(markTaskAssembler.toShiftDTO(m)));
+        return new PageableDTO(markTaskDTOs, markTasks.getTotalElements(), markTasks.getTotalPages(), pageable.getPageNumber());
+    }
+
     /**
      * randomSeqNew排序
      *

+ 45 - 2
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/api/PaperApi.java

@@ -32,6 +32,7 @@ import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
+import javax.persistence.criteria.CriteriaBuilder;
 import javax.persistence.criteria.Predicate;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -143,6 +144,7 @@ public class PaperApi {
                                      @RequestParam(required = false) Long endNumber,
                                      @RequestParam(required = false) Boolean isManual,
                                      @RequestParam(required = false) Boolean missing,
+                                     @RequestParam(required = false) Boolean isRelate,
                                      @RequestParam(required = false) String studentName,
                                      @RequestParam(required = false) String sortBy,
                                      Pageable pageable) {
@@ -176,6 +178,9 @@ public class PaperApi {
             if (missing != null) {
                 predicates.add(builder.equal(root.get("isMissing"), missing));
             }
+            if (isRelate != null) {
+                predicates.add(builder.equal(root.get("isRelate"), isRelate));
+            }
             //考生姓名
             if (studentName != null && studentName != "") {
                 predicates.add(builder.equal(root.get("studentName"), studentName));
@@ -216,7 +221,7 @@ public class PaperApi {
             List<Predicate> predicates = new ArrayList<>();
             predicates.add(builder.equal(root.get("questionId"), questionId));
             //isScore为true时,为科组长打分详情页面数据查询条件
-            if(isScore) {
+            if (isScore) {
                 //level为null时,查询待评数量
                 if (Objects.isNull(level)) {
                     predicates.add(builder.isNotNull(root.get("level")));
@@ -257,7 +262,45 @@ public class PaperApi {
         papers.forEach(p -> {
             paperDTOs.add(paperAssembler.toDTO(p));
         });
-        paperDTOs.sort(Comparator.comparingInt(PaperDTO::getSortSum));
+        paperDTOs.sort((o1, o2) -> o2.getSortSum() -o1.getSortSum());
+        return new PageableDTO(paperDTOs, papers.getTotalElements(), papers.getTotalPages(), pageable.getPageNumber());
+    }
+
+    /**
+     * 科组长的输分试卷
+     * @param workId
+     * @param questionId
+     * @param pageable
+     * @return
+     */
+    @RequestMapping(value = "/manualScore", method = RequestMethod.GET)
+    public PageableDTO listManualScore(@RequestParam(required = false) Long workId,
+                                       @RequestParam Long questionId,
+                                       Pageable pageable) {
+        List<MarkTask> markTasks = markTaskRepo.findByWorkIdAndQuestionIdAndStageAndManualScore(workId, questionId, MarkStage.SCORE, 1);
+        Set<Long> paperIds = markTasks.stream().map(m->m.getPaper().getId()).collect(Collectors.toSet());
+        Specification<Paper> specification = (root, query, builder) -> {
+            List<Predicate> predicates = new ArrayList<>();
+            predicates.add(builder.isNotNull(root.get("score")));
+            if (paperIds != null && paperIds.size() > 0) {
+                CriteriaBuilder.In<Object> in = builder.in(root.get("id"));
+                for (Long paperId : paperIds) {
+                    in.value(paperId);
+                }
+                predicates.add(builder.and(builder.and(in)));
+            } else {
+                // 必查空
+                predicates.add(builder.isNull(root.get("id")));
+            }
+            return builder.and(predicates.toArray(new Predicate[predicates.size()]));
+        };
+
+        Page<Paper> papers = paperRepo.findAll(specification, pageable);
+        List<PaperDTO> paperDTOs = new ArrayList<>();
+        papers.forEach(p -> {
+            paperDTOs.add(paperAssembler.toDTO(p));
+        });
+        paperDTOs.sort((o1, o2) -> o2.getSortSum() -o1.getSortSum());
         return new PageableDTO(paperDTOs, papers.getTotalElements(), papers.getTotalPages(), pageable.getPageNumber());
     }
 

+ 1 - 1
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/assembler/PaperAssembler.java

@@ -122,7 +122,7 @@ public class PaperAssembler {
             if(markSubject.getTest() == 0 && Objects.nonNull(paper.getLevel())) {
                 List<Level> levels = levelRepo.findByWorkId(paper.getWorkId());
                 if(!levels.isEmpty() && levels.size() > 0){
-                    Map<String, Integer> map = levels.stream().collect(Collectors.toMap(Level::getCode, Level::getLevelValue));
+                    Map<String, Integer> map = levels.stream().collect(Collectors.toMap(Level::getCode, Level::getWeight));
                     List<MarkTask> markTaskList = markTaskRepo.findByPaperIdAndStage(paper.getId(), MarkStage.LEVEL);
                     int sum = markTaskList.stream().mapToInt(m-> map.get(m.getResult()) == null ? 0 : map.get(m.getResult())).sum();
                     paperDTO.setSortSum(sum);

+ 1 - 0
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/dto/LevelStatDTO.java

@@ -149,4 +149,5 @@ public class LevelStatDTO implements Serializable {
     public void setFinalTotal(Integer finalTotal) {
         this.finalTotal = finalTotal;
     }
+
 }

+ 25 - 0
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/service/AssignTaskService.java

@@ -1,17 +1,22 @@
 package cn.com.qmth.stmms.ms.marking.service;
 
+import cn.com.qmth.stmms.ms.commons.utils.DbBackupUtils;
 import cn.com.qmth.stmms.ms.commons.utils.RandomUtil;
+import cn.com.qmth.stmms.ms.commons.utils.ServletUtil;
 import cn.com.qmth.stmms.ms.commons.utils.SqlUtil;
+import cn.com.qmth.stmms.ms.core.cache.CacheService;
 import cn.com.qmth.stmms.ms.core.cache.ParamCache;
 import cn.com.qmth.stmms.ms.core.domain.*;
 import cn.com.qmth.stmms.ms.core.domain.task.MarkTask;
 import cn.com.qmth.stmms.ms.core.domain.user.MarkUser;
 import cn.com.qmth.stmms.ms.core.domain.user.MarkerGroup;
+import cn.com.qmth.stmms.ms.core.domain.user.Role;
 import cn.com.qmth.stmms.ms.core.repository.*;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 import java.util.*;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -52,6 +57,15 @@ public class AssignTaskService {
     @Autowired
     SqlUtil sqlUtil;
 
+    @Autowired
+    DbBackupUtils dbBackupUtils;
+
+    @Autowired
+    CacheService cacheService;
+
+    @Autowired
+    MarkUserRepo markUserRepo;
+
     /**
      * 单评任务模式,每个科目评卷员都需要对该科目每份试卷进行评卷
      * 比如,色彩有1000份试卷,那么每个评卷员都需要评1000份
@@ -274,6 +288,17 @@ public class AssignTaskService {
             //初始化打分任务数据
             initTaskPublishData(markSubject);
         }
+
+        // 所有用户强制退出
+        List<MarkUser> userList = markUserRepo.findByWorkIdAndSubject(markSubject.getWorkId(), markSubject.getSubject());
+        userList = userList.stream().filter(m-> Role.MARKER.equals(m.getRole()) || Role.MARK_LEADER.equals(m.getRole())).collect(Collectors.toList());
+        if(!CollectionUtils.isEmpty(userList)){
+            for (MarkUser user : userList) {
+                cacheService.deleteTokenCache(user.getId().toString());
+            }
+        }
+        // 备份数据库
+        dbBackupUtils.startBackup();
     }
 
     private void initTaskPublishData(MarkSubject markSubject) {

+ 1 - 0
stmms-ms-marking/src/main/java/cn/com/qmth/stmms/ms/marking/service/MarkingService.java

@@ -119,6 +119,7 @@ public class MarkingService {
         markTask.setResult(levelCode);
         markTask.setLevelValue(level.getLevelValue());
         markTask.setRejected(false);
+        markTask.setDeviationDirection(null);
         markTask.setUpdatedOn(new Date());
         markTask.setMarkerName(maker.getName());
         //激活试卷