Explorar o código

Merge branch 'dev_200331_multimedia' into dev_200331_openapi

Conflicts:
	stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkCronService.java
	stmms-biz/src/main/resources/service-context.xml
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ImageCheckController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/LibraryController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkGroupController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkQualityController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/PaperController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ScoreCheckController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/StudentController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/TrialController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/admin/user/UserController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/api/controller/ExamInfoController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/BaseController.java
	stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/LoginController.java
	stmms-web/src/main/webapp/WEB-INF/application.properties
	stmms-web/src/main/webapp/WEB-INF/spring-mvc.xml
	stmms-web/src/main/webapp/WEB-INF/views/modules/exam/markerList.jsp
	stmms-web/src/main/webapp/sql/stmms_ft.sql
ting.yin %!s(int64=5) %!d(string=hai) anos
pai
achega
795fb67ddb
Modificáronse 100 ficheiros con 4666 adicións e 742 borrados
  1. 10 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/DataSyncDao.java
  2. 164 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/DataSync.java
  3. 19 4
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/Exam.java
  4. 0 14
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/ExamQuestion.java
  5. 12 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/ExamStudent.java
  6. 16 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/query/ExamSearchQuery.java
  7. 11 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/DataSyncService.java
  8. 29 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/impl/DataSyncServiceImpl.java
  9. 11 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/impl/ExamServiceImpl.java
  10. 34 34
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/MarkLibraryDao.java
  11. 2 2
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/ProblemHistoryDao.java
  12. 13 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/Task.java
  13. 36 19
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkCronService.java
  14. 171 94
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/MarkServiceImpl.java
  15. 5 2
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/ProblemHistoryServiceImpl.java
  16. 10 0
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/TaskServiceImpl.java
  17. 36 27
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/MarkService.java
  18. 1 1
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/ProblemHistoryService.java
  19. 10 11
      stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/BaseCalculatorUnit.java
  20. 1 1
      stmms-biz/src/main/resources/service-context.xml
  21. 34 0
      stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/ExamType.java
  22. 1 1
      stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/LockType.java
  23. 0 31
      stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/ProblemType.java
  24. 16 4
      stmms-common/src/main/java/cn/com/qmth/stmms/common/utils/PictureUrlBuilder.java
  25. 0 1
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/ObjectiveQuestionDTO.java
  26. 3 4
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/SubjectiveQuestionDTO.java
  27. 19 6
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/AnswerCheckController.java
  28. 19 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ArbitrateController.java
  29. 187 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/DataSyncController.java
  30. 7 3
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ExamController.java
  31. 2 1
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ImageCheckController.java
  32. 53 6
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/LibraryController.java
  33. 90 60
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkGroupController.java
  34. 41 21
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkQualityController.java
  35. 19 17
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/PaperController.java
  36. 118 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ProblemHistoryController.java
  37. 2 3
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ProblemTypeController.java
  38. 2 2
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ScoreCheckController.java
  39. 8 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ScoreController.java
  40. 86 17
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/StudentController.java
  41. 50 14
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/TrialController.java
  42. 210 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/DataSyncThread.java
  43. 2 2
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/ScoreCheckThread.java
  44. 169 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/SubjectScoreCalculateThread.java
  45. 155 156
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/user/UserController.java
  46. 116 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/ByteUtil.java
  47. 210 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/HttpUtil.java
  48. 25 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/SHA256.java
  49. 8 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/UpyunConfig.java
  50. 29 0
      stmms-web/src/main/java/cn/com/qmth/stmms/admin/vo/UploadStudentVO.java
  51. 8 7
      stmms-web/src/main/java/cn/com/qmth/stmms/api/controller/ExamInfoController.java
  52. 5 2
      stmms-web/src/main/java/cn/com/qmth/stmms/api/controller/ScanController.java
  53. 35 12
      stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/BaseController.java
  54. 13 12
      stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/LoginController.java
  55. 18 4
      stmms-web/src/main/java/cn/com/qmth/stmms/mark/MarkController.java
  56. 59 0
      stmms-web/src/main/java/cn/com/qmth/stmms/monitor/ScoreMonitorController.java
  57. 19 4
      stmms-web/src/main/webapp/WEB-INF/application.properties
  58. 2 1
      stmms-web/src/main/webapp/WEB-INF/spring-mvc.xml
  59. 2 1
      stmms-web/src/main/webapp/WEB-INF/views/include/head.jsp
  60. 1 0
      stmms-web/src/main/webapp/WEB-INF/views/include/taglib.jsp
  61. 93 9
      stmms-web/src/main/webapp/WEB-INF/views/include/trialDetail.jsp
  62. 3 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/arbitrateBatchProcess.jsp
  63. 86 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/arbitrateBatchProcessJson.jsp
  64. 1 1
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/arbitrateList.jsp
  65. 3 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/arbitrateSingleProcess.jsp
  66. 83 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/arbitrateSingleProcessJson.jsp
  67. 97 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/dataSync.jsp
  68. 12 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/examEdit.jsp
  69. 19 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/examForm.jsp
  70. 2 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/examIndex.jsp
  71. 5 2
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/examList.jsp
  72. 8 4
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupAdd.jsp
  73. 9 5
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupEditFull.jsp
  74. 6 2
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupEditSimple.jsp
  75. 5 1
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/groupList.jsp
  76. 122 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/jsonView.jsp
  77. 9 2
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/libraryList.jsp
  78. 6 2
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/markerList.jsp
  79. 5 1
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/paperList.jsp
  80. 12 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/permission.jsp
  81. 149 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/problemHistory.jsp
  82. 4 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/qualityProcess.jsp
  83. 74 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/qualityProcessJson.jsp
  84. 10 2
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/scoreList.jsp
  85. 25 3
      stmms-web/src/main/webapp/WEB-INF/views/modules/exam/studentList.jsp
  86. 104 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markJson.jsp
  87. 4 1
      stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markNew.jsp
  88. 3 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markTrack.jsp
  89. 4 0
      stmms-web/src/main/webapp/WEB-INF/views/modules/mark/picConfig.jsp
  90. 14 13
      stmms-web/src/main/webapp/WEB-INF/views/modules/mark/reset.jsp
  91. 60 52
      stmms-web/src/main/webapp/WEB-INF/views/modules/sys/login.jsp
  92. 1 1
      stmms-web/src/main/webapp/WEB-INF/views/modules/user/userEdit.jsp
  93. 64 40
      stmms-web/src/main/webapp/sql/stmms_ft.sql
  94. 506 0
      stmms-web/src/main/webapp/static/i18n/jquery.i18n.properties.js
  95. 37 0
      stmms-web/src/main/webapp/static/i18n/load.js
  96. 138 0
      stmms-web/src/main/webapp/static/i18n/messages.properties
  97. 138 0
      stmms-web/src/main/webapp/static/i18n/messages_en.properties
  98. 138 0
      stmms-web/src/main/webapp/static/i18n/messages_ja.properties
  99. 138 0
      stmms-web/src/main/webapp/static/i18n/messages_zh.properties
  100. 35 0
      stmms-web/src/main/webapp/static/mark-json/js/json-loader.js

+ 10 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/dao/DataSyncDao.java

@@ -0,0 +1,10 @@
+package cn.com.qmth.stmms.biz.exam.dao;
+
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.repository.PagingAndSortingRepository;
+
+import cn.com.qmth.stmms.biz.exam.model.DataSync;
+
+public interface DataSyncDao extends PagingAndSortingRepository<DataSync, Integer>, JpaSpecificationExecutor<DataSync> {
+
+}

+ 164 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/DataSync.java

@@ -0,0 +1,164 @@
+package cn.com.qmth.stmms.biz.exam.model;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+
+@Entity
+@Table(name = "eb_data_sync")
+public class DataSync implements Serializable {
+
+    private static final long serialVersionUID = 7371535739573217406L;
+
+    @Id
+    private Integer examId;
+
+    @Column(name = "school_id")
+    private Integer schoolId;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "create_time")
+    private Date createTime;
+
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "update_time")
+    private Date updateTime;
+
+    @Column(name = "root_org_id", nullable = false)
+    private String rootOrgId;
+
+    @Column(name = "cloud_exam_id", nullable = false)
+    private Long cloudExamId;
+
+    @Column(name = "subject_code")
+    private String subjectCode;
+
+    @Column(name = "next_id")
+    private Long nextId;
+
+    @Column(name = "student_url", nullable = false)
+    private String studentUrl;
+
+    @Column(name = "app_id", nullable = false)
+    private String appId;
+
+    @Column(name = "secret_key", nullable = false)
+    private String secretKey;
+
+    @Column(name = "finished", nullable = false)
+    private boolean finished;
+
+    @Column(name = "subject_url", nullable = false)
+    private String subjectUrl;
+
+    public Integer getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Integer examId) {
+        this.examId = examId;
+    }
+
+    public Integer getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Integer schoolId) {
+        this.schoolId = schoolId;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(String rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public Long getCloudExamId() {
+        return cloudExamId;
+    }
+
+    public void setCloudExamId(Long cloudExamId) {
+        this.cloudExamId = cloudExamId;
+    }
+
+    public String getSubjectCode() {
+        return subjectCode;
+    }
+
+    public void setSubjectCode(String subjectCode) {
+        this.subjectCode = subjectCode;
+    }
+
+    public Long getNextId() {
+        return nextId;
+    }
+
+    public void setNextId(Long nextId) {
+        this.nextId = nextId;
+    }
+
+    public String getStudentUrl() {
+        return studentUrl;
+    }
+
+    public void setStudentUrl(String studentUrl) {
+        this.studentUrl = studentUrl;
+    }
+
+    public boolean isFinished() {
+        return finished;
+    }
+
+    public void setFinished(boolean finished) {
+        this.finished = finished;
+    }
+
+    public String getSubjectUrl() {
+        return subjectUrl;
+    }
+
+    public void setSubjectUrl(String subjectUrl) {
+        this.subjectUrl = subjectUrl;
+    }
+
+    public String getAppId() {
+        return appId;
+    }
+
+    public void setAppId(String appId) {
+        this.appId = appId;
+    }
+
+    public String getSecretKey() {
+        return secretKey;
+    }
+
+    public void setSecretKey(String secretKey) {
+        this.secretKey = secretKey;
+    }
+
+}

+ 19 - 4
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/Exam.java

@@ -16,6 +16,7 @@ import javax.persistence.TemporalType;
 
 import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
+import cn.com.qmth.stmms.common.enums.ExamType;
 
 @Entity
 @Table(name = "eb_exam")
@@ -97,13 +98,20 @@ public class Exam implements Serializable {
      */
     @Column(name = "sas_config", nullable = true)
     private String sasConfig;
-    
+
     /**
      * 原图遮盖配置
      */
     @Column(name = "sheet_config", nullable = true)
     private String sheetConfig;
 
+    /**
+     * 考试类型
+     */
+    @Enumerated(EnumType.STRING)
+    @Column(name = "type", length = 16, nullable = false)
+    private ExamType type;
+
     public Integer getId() {
         return id;
     }
@@ -236,17 +244,24 @@ public class Exam implements Serializable {
         this.sasConfig = sasConfig;
     }
 
-    
     public String getSheetConfig() {
         return sheetConfig;
     }
-    
+
     public void setSheetConfig(String sheetConfig) {
         this.sheetConfig = sheetConfig;
     }
-    
+
     public List<PictureConfigItem> getSheetConfigList() {
         return PictureConfigItem.parse(sheetConfig);
     }
 
+    public ExamType getType() {
+        return type;
+    }
+
+    public void setType(ExamType type) {
+        this.type = type;
+    }
+
 }

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

@@ -58,12 +58,6 @@ public class ExamQuestion implements Serializable {
     @Column(name = "interval_score", nullable = false)
     private Double intervalScore;
 
-    /**
-     * 考生人数
-     */
-    @Column(name = "total_count", nullable = true)
-    private Integer totalCount;
-
     /**
      * 客观题判分策略(1-平均,2-最高,3-最低)
      */
@@ -211,14 +205,6 @@ public class ExamQuestion implements Serializable {
         this.intervalScore = intervalScore;
     }
 
-    public Integer getTotalCount() {
-        return totalCount;
-    }
-
-    public void setTotalCount(Integer totalCount) {
-        this.totalCount = totalCount;
-    }
-
     public ExamSubject getSubject() {
         return subject;
     }

+ 12 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/model/ExamStudent.java

@@ -17,6 +17,7 @@ import javax.persistence.Transient;
 
 import org.apache.commons.lang.StringUtils;
 
+import cn.com.qmth.stmms.biz.mark.model.ProblemType;
 import cn.com.qmth.stmms.biz.utils.ScoreItem;
 import cn.com.qmth.stmms.common.annotation.ExcelField;
 
@@ -288,6 +289,9 @@ public class ExamStudent implements Serializable {
     @Transient
     private List<ExamQuestion> objectiveQuestionList;
 
+    @Transient
+    private ProblemType problemType;
+
     public Integer getId() {
         return id;
     }
@@ -752,4 +756,12 @@ public class ExamStudent implements Serializable {
         this.objectiveQuestionList = objectiveQuestionList;
     }
 
+    public ProblemType getProblemType() {
+        return problemType;
+    }
+
+    public void setProblemType(ProblemType problemType) {
+        this.problemType = problemType;
+    }
+
 }

+ 16 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/query/ExamSearchQuery.java

@@ -9,6 +9,7 @@ import org.springframework.data.domain.Sort.Direction;
 import cn.com.qmth.stmms.biz.common.BaseQuery;
 import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
+import cn.com.qmth.stmms.common.enums.ExamType;
 
 public class ExamSearchQuery extends BaseQuery<Exam> {
 
@@ -22,6 +23,8 @@ public class ExamSearchQuery extends BaseQuery<Exam> {
 
     private Set<Integer> ids;
 
+    private Set<ExamType> typeSet;
+
     public void orderByIdDesc() {
         setSort(new Sort(Direction.DESC, "id"));
     }
@@ -55,6 +58,19 @@ public class ExamSearchQuery extends BaseQuery<Exam> {
         }
     }
 
+    public Set<ExamType> getTypeSet() {
+        return typeSet;
+    }
+
+    public void addTypes(ExamType type) {
+        if (typeSet == null) {
+            typeSet = new HashSet<ExamType>();
+        }
+        if (type != null) {
+            typeSet.add(type);
+        }
+    }
+
     public Set<Integer> getIds() {
         return ids;
     }

+ 11 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/exam/service/DataSyncService.java

@@ -0,0 +1,11 @@
+package cn.com.qmth.stmms.biz.exam.service;
+
+import cn.com.qmth.stmms.biz.exam.model.DataSync;
+
+public interface DataSyncService {
+
+    DataSync save(DataSync exam);
+
+    DataSync findByExamId(Integer examId);
+
+}

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

@@ -0,0 +1,29 @@
+package cn.com.qmth.stmms.biz.exam.service.impl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import cn.com.qmth.stmms.biz.common.BaseQueryService;
+import cn.com.qmth.stmms.biz.exam.dao.DataSyncDao;
+import cn.com.qmth.stmms.biz.exam.model.DataSync;
+import cn.com.qmth.stmms.biz.exam.service.DataSyncService;
+
+@Service
+public class DataSyncServiceImpl extends BaseQueryService<DataSync> implements DataSyncService {
+
+    @Autowired
+    private DataSyncDao dataSyncDao;
+
+    @Transactional
+    @Override
+    public DataSync save(DataSync dataSync) {
+        return dataSyncDao.save(dataSync);
+    }
+
+    @Override
+    public DataSync findByExamId(Integer examId) {
+        return dataSyncDao.findOne(examId);
+    }
+
+}

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

@@ -25,6 +25,7 @@ import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.biz.mark.model.ProblemType;
 import cn.com.qmth.stmms.biz.mark.service.ProblemTypeService;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
+import cn.com.qmth.stmms.common.enums.ExamType;
 
 @Service
 public class ExamServiceImpl extends BaseQueryService<Exam> implements ExamService {
@@ -126,6 +127,16 @@ public class ExamServiceImpl extends BaseQueryService<Exam> implements ExamServi
                     }
                 }
 
+                if (query.getTypeSet() != null && query.getTypeSet().size() > 0) {
+                    List<Predicate> typePredicates = new LinkedList<Predicate>();
+                    for (ExamType type : query.getTypeSet()) {
+                        typePredicates.add(cb.equal(root.get("type"), type));
+                    }
+                    if (!typePredicates.isEmpty()) {
+                        predicates.add(cb.or(typePredicates.toArray(new Predicate[typePredicates.size()])));
+                    }
+                }
+
                 if (query.getIds() != null && query.getIds().size() > 0) {
                     List<Predicate> idsPredicates = new LinkedList<Predicate>();
                     for (Integer id : query.getIds()) {

+ 34 - 34
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/MarkLibraryDao.java

@@ -4,7 +4,9 @@ import java.util.Date;
 import java.util.List;
 import java.util.Set;
 
+import cn.com.qmth.stmms.common.enums.MarkStatus;
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Modifying;
@@ -17,78 +19,77 @@ public interface MarkLibraryDao extends JpaRepository<MarkLibrary, Integer>, Jpa
 
     List<MarkLibrary> findByExamIdAndSubjectCode(Integer examId, String subjectCode, Pageable page);
 
-    List<MarkLibrary> findByExamIdAndSubjectCodeAndGroupNumberAndStatus(Integer examId, String subjectCode,
-            Integer groupNumber, LibraryStatus status, Pageable page);
+    List<MarkLibrary> findByExamIdAndSubjectCodeAndGroupNumberAndStatus(Integer examId, String subjectCode, Integer groupNumber,
+            LibraryStatus status, Pageable page);
 
-    List<MarkLibrary> findByExamIdAndSubjectCodeAndGroupNumberAndStatusIn(Integer examId, String subjectCode,
-            Integer groupNumber, Set<LibraryStatus> statusSet, Pageable page);
+    List<MarkLibrary> findByExamIdAndSubjectCodeAndGroupNumberAndStatusIn(Integer examId, String subjectCode, Integer groupNumber,
+            Set<LibraryStatus> statusSet, Pageable page);
 
     @Query("select l1 from MarkLibrary l1 where l1.examId=?1 and l1.subjectCode=?2 and l1.groupNumber=?3 and l1.status in (?5) "
             + "and not exists (select l2 from MarkLibrary l2 where l2.studentId=l1.studentId and l2.id!=l1.id and l2.markerId=?4)")
-    List<MarkLibrary> findUnMarked(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
-            Set<LibraryStatus> statusSet, Pageable page);
+    List<MarkLibrary> findUnMarked(Integer examId, String subjectCode, Integer groupNumber, Integer markerId, Set<LibraryStatus> statusSet,
+            Pageable page);
 
     @Query("select l1 from MarkLibrary l1 where l1.examId=?1 and l1.subjectCode=?2 and l1.groupNumber=?3 and l1.status in (?5) "
             + "and not exists (select l2 from MarkLibrary l2 where l2.studentId=l1.studentId and l2.id!=l1.id and l2.markerId=?4) "
             + "and exists (select mc.id from MarkerClass mc, ExamStudent s where l1.studentId=s.id and mc.markerId=?4 and s.className=mc.className)")
-    List<MarkLibrary> findUnMarkedFilterClass(Integer examId, String subjectCode, Integer groupNumber,
-            Integer markerId, Set<LibraryStatus> statusSet, Pageable page);
+    List<MarkLibrary> findUnMarkedFilterClass(Integer examId, String subjectCode, Integer groupNumber, Integer markerId,
+            Set<LibraryStatus> statusSet, Pageable page);
 
     List<MarkLibrary> findByMarkerId(Integer markerId);
 
+    List<MarkLibrary> findByMarkerIdAndStatus(Integer markerId, LibraryStatus status, Pageable page);
+
     @Query("select l from MarkLibrary l where l.studentId=?1 order by l.groupNumber ")
     List<MarkLibrary> findByStudentId(Integer studentId);
 
     List<MarkLibrary> findByStudentIdAndGroupNumber(Integer studentId, Integer groupNumber);
 
     @Query("select l from MarkLibrary l where l.studentId=?1 and l.groupNumber=?2 and l.status in (?3) ")
-    List<MarkLibrary> findByStudentIdAndGroupNumberAndStatus(Integer studentId, Integer groupNumber,
-            LibraryStatus... status);
+    List<MarkLibrary> findByStudentIdAndGroupNumberAndStatus(Integer studentId, Integer groupNumber, LibraryStatus... status);
 
-    @Query("select count(*) from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3")
+    @Query("select count(f) from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3")
     long countByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber);
 
-    @Query("select count(*) from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3 and f.taskNumber=?4")
-    long countByExamIdAndSubjectCodeAndGroupNumberAndTaskNumber(Integer examId, String subjectCode,
-            Integer groupNumber, Integer taskNumber);
+    @Query("select count(f) from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3 and f.taskNumber=?4")
+    long countByExamIdAndSubjectCodeAndGroupNumberAndTaskNumber(Integer examId, String subjectCode, Integer groupNumber,
+            Integer taskNumber);
 
-    @Query("select count(*) from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3 and f.status in (?4)")
+    @Query("select count(f) from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.groupNumber=?3 and f.status in (?4)")
     long countByExamIdAndSubjectCodeAndGroupNumberAndStatus(Integer examId, String subjectCode, Integer groupNumber,
             LibraryStatus... status);
 
-    @Query("select count(*) from MarkLibrary f where f.studentId=?1 and f.groupNumber=?2")
+    @Query("select count(f) from MarkLibrary f where f.studentId=?1 and f.groupNumber=?2")
     long countByStudentIdAndGroupNumber(Integer studentId, Integer groupNumber);
 
-    @Query("select count(*) from MarkLibrary f where f.studentId=?1 and f.markerId=?2")
+    @Query("select count(f) from MarkLibrary f where f.studentId=?1 and f.markerId=?2")
     long countByStudentIdAndMarkerId(Integer studentId, Integer markerId);
 
-    @Query("select count(*) from MarkLibrary f where f.studentId=?1 and f.markerId=?2 and f.id!=?3")
+    @Query("select count(f) from MarkLibrary f where f.studentId=?1 and f.markerId=?2 and f.id!=?3")
     long countByStudentIdAndMarkerIdAndIdNotEqual(Integer studentId, Integer markerId, Integer id);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary m set m.status=?4, m.markerId=null, m.markerTime=null, m.markerScore=null, m.markerScoreList=null, m.markerSpent=null, "
             + "m.headerId=null , m.headerTime=null , m.headerScore=null , m.headerScoreList=null where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3")
-    void resetByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber,
-            LibraryStatus status);
+    void resetByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber, LibraryStatus status);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary m set m.status=?2, m.markerId=null, m.markerTime=null, m.markerScore=null, m.markerScoreList=null, m.markerSpent=null, "
             + "m.headerId=null , m.headerTime=null , m.headerScore=null , m.headerScoreList=null  where m.markerId=?1 and m.status!=?3 and m.status not in (?4) ")
-    void resetByMarkerId(Integer markerId, LibraryStatus status, LibraryStatus libraryStatus1,
-            LibraryStatus... libraryStatus);
+    void resetByMarkerId(Integer markerId, LibraryStatus status, LibraryStatus libraryStatus1, LibraryStatus... libraryStatus);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary m set m.status=?2, m.markerId=null, m.markerTime=null, m.markerScore=null, m.markerScoreList=null, m.markerSpent=null, "
             + "m.headerId=null , m.headerTime=null , m.headerScore=null , m.headerScoreList=null where m.id=?1 and m.status in (?3)")
     int resetById(Integer id, LibraryStatus newStatus, LibraryStatus... previousStatus);
 
-    @Query("select f.markerId, count(*) as markerCount from MarkLibrary f where f.examId=?1 and f.status in (?2) group by f.markerId")
+    @Query("select f.markerId, count(f) as markerCount from MarkLibrary f where f.examId=?1 and f.status in (?2) group by f.markerId")
     List<Object[]> countMarkerAndStatus(Integer examId, LibraryStatus... status);
 
-    @Query("select f.markerId, count(*) as markerCount from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.status in (?3) group by f.markerId")
+    @Query("select f.markerId, count(f) as markerCount from MarkLibrary f where f.examId=?1 and f.subjectCode=?2 and f.status in (?3) group by f.markerId")
     List<Object[]> countMarkerAndStatus(Integer examId, String subjectCode, LibraryStatus... status);
 
-    @Query("select count(*) from MarkLibrary f where f.markerId=?1 and f.status in (?2)")
+    @Query("select count(f) from MarkLibrary f where f.markerId=?1 and f.status in (?2)")
     long countByMarkerAndStatus(Integer markerId, LibraryStatus... status);
 
     @Modifying
@@ -105,8 +106,8 @@ public interface MarkLibraryDao extends JpaRepository<MarkLibrary, Integer>, Jpa
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary m set m.headerId=?3, m.headerScore=?4, m.headerScoreList=?5, m.headerTime=?6, m.status=?7 "
             + " where m.studentId=?1 and m.groupNumber=?2")
-    void updateHeaderResult(Integer studentId, Integer groupNumber, Integer userId, Double totalScore,
-            String scoreList, Date updateTime, LibraryStatus arbitrated);
+    void updateHeaderResult(Integer studentId, Integer groupNumber, Integer userId, Double totalScore, String scoreList, Date updateTime,
+            LibraryStatus arbitrated);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary m set m.status=?3 where m.studentId=?1 and m.groupNumber=?2")
@@ -115,20 +116,19 @@ public interface MarkLibraryDao extends JpaRepository<MarkLibrary, Integer>, Jpa
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary l set l.status=?2, l.markerId=?3, l.markerScore=?4, l.markerScoreList=?5, l.markerTime=?6, "
             + "l.markerSpent=?7 where l.id=?1 and l.status in (?8) and (l.markerId=null or l.markerId=?3)")
-    int updateMarkerResult(Integer id, LibraryStatus newStatus, Integer markerId, Double markerScore,
-            String markerScoreList, Date markerTime, Integer spent, LibraryStatus... previousStatus);
+    int updateMarkerResult(Integer id, LibraryStatus newStatus, Integer markerId, Double markerScore, String markerScoreList,
+            Date markerTime, Integer spent, LibraryStatus... previousStatus);
 
-    @Query("select m.markerScore ,count(*) from MarkLibrary m where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3 and m.markerId=?4 and m.status in (?5) group by m.markerScore ")
-    List<Object[]> findScoreCount(int examId, String subjectCode, Integer groupNumber, Integer markerId,
-            LibraryStatus... status);
+    @Query("select m.markerScore ,count(m) from MarkLibrary m where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3 and m.markerId=?4 and m.status in (?5) group by m.markerScore ")
+    List<Object[]> findScoreCount(int examId, String subjectCode, Integer groupNumber, Integer markerId, LibraryStatus... status);
 
     @Query("select m.markerScore from MarkLibrary m where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3 and m.status in (?4) group by m.markerScore ")
     List<Double> findScore(int examId, String subjectCode, Integer groupNumber, LibraryStatus... status);
 
     @Modifying
     @Query("delete MarkLibrary m where m.examId=?1 and m.subjectCode=?2 and m.groupNumber=?3 and m.taskNumber=?4")
-    void deleteByExamIdAndSubjectCodeAndGroupNumberAndTaskNumber(Integer examId, String subjectCode,
-            Integer groupNumber, Integer taskNumber);
+    void deleteByExamIdAndSubjectCodeAndGroupNumberAndTaskNumber(Integer examId, String subjectCode, Integer groupNumber,
+            Integer taskNumber);
 
     @Modifying(clearAutomatically = true)
     @Query("update MarkLibrary l set l.status=?2, l.markerId=?3, l.markerTime=?4, "

+ 2 - 2
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/dao/ProblemHistoryDao.java

@@ -33,8 +33,6 @@ public interface ProblemHistoryDao extends JpaRepository<ProblemHistory, Integer
     @Query("select distinct l.subjectCode from ProblemHistory l where l.examId=?1 ")
     List<String> findProblemSubjectCode(Integer examId);
 
-    ProblemHistory findByStudentIdAndStatus(Integer studentId, HistoryStatus status);
-
     @Modifying
     @Query("delete from ProblemHistory s where s.studentId in (select m.studentId from MarkLibrary m where m.markerId=?1 ) ")
     void deleteByMarkerId(Integer markerId);
@@ -43,4 +41,6 @@ public interface ProblemHistoryDao extends JpaRepository<ProblemHistory, Integer
     @Query("delete from ProblemHistory s where s.examId=?1 and s.subjectCode=?2 and s.groupNumber=?3  ")
     void deleteByExamIdAndSubjectCodeAndGroupNumber(Integer examId, String subjectCode, Integer groupNumber);
 
+    ProblemHistory findByLibraryIdAndStatus(Integer libraryId, HistoryStatus status);
+
 }

+ 13 - 0
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/model/Task.java

@@ -51,6 +51,11 @@ public class Task extends MarkResult implements Serializable {
      */
     private String paperUrl;
 
+    /**
+     * 多媒体答案地址
+     */
+    private String jsonUrl;
+
     /**
      * 客观题总分
      */
@@ -317,4 +322,12 @@ public class Task extends MarkResult implements Serializable {
         this.markerId = markerId;
     }
 
+    public String getJsonUrl() {
+        return jsonUrl;
+    }
+
+    public void setJsonUrl(String jsonUrl) {
+        this.jsonUrl = jsonUrl;
+    }
+
 }

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

@@ -63,6 +63,9 @@ public class MarkCronService {
     @Value("${mark.cleanTimeoutMinute}")
     private long timeoutMinute;
 
+    @Value("${mark.activeExpireMinute}")
+    private long markerActiveExpireMinute;
+
     /**
      * 自动释放可清除的锁
      */
@@ -95,7 +98,7 @@ public class MarkCronService {
     public void buildLibrary() {
         log.info("start auto-create library");
         try {
-            Map<String, Campus> campusMap = new HashMap<String, Campus>();
+            Map<String, Campus> campusMap = new HashMap<>();
             List<Integer> examIds = groupService.findExamIdByStatus(MarkStatus.TRIAL, MarkStatus.FORMAL);
             for (Integer examId : examIds) {
                 buildLibraryByExam(examId, campusMap);
@@ -112,7 +115,7 @@ public class MarkCronService {
      */
     @Scheduled(fixedDelay = 60 * 1000, initialDelay = 60 * 1000)
     public void updateMarkerQuality() {
-        log.debug("start auto-update marker");
+        log.info("start auto-update marker");
         try {
             List<Integer> examIds = groupService.findExamIdByStatus(MarkStatus.FORMAL);
             for (Integer examId : examIds) {
@@ -121,7 +124,7 @@ public class MarkCronService {
         } catch (Exception e) {
             log.error("auto-update marker error", e);
         } finally {
-            log.debug("finish auto-update marker");
+            log.info("finish auto-update marker");
         }
     }
 
@@ -129,12 +132,17 @@ public class MarkCronService {
         // 获取主观题总分大于0的科目
         List<ExamSubject> subjects = subjectService.list(examId, 0);
         for (ExamSubject subject : subjects) {
-            List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(examId, subject.getCode(), MarkStatus.FORMAL);
+            List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(examId, subject.getCode(),
+                    MarkStatus.FORMAL);
             for (MarkGroup markGroup : groups) {
-                List<Marker> markers = markerService.findByExamAndSubjectAndGroup(examId, subject.getCode(), markGroup.getNumber());
+                List<Marker> markers = markerService.findByExamAndSubjectAndGroup(examId, subject.getCode(),
+                        markGroup.getNumber());
                 for (Marker marker : markers) {
                     try {
-                        markService.updateQuality(marker);
+                        if (markService.needUpdateQuality(marker, markerActiveExpireMinute)) {
+                            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);
                     }
@@ -148,7 +156,8 @@ public class MarkCronService {
         List<ExamSubject> subjects = subjectService.listSubjectiveScore(examId, 0d);
         for (ExamSubject subject : subjects) {
             // 清除缺考考生和违纪考生
-            List<ExamStudent> list = studentService.findAbsentOrBreachLibraryStudent(subject.getExamId(), subject.getCode());
+            List<ExamStudent> list = studentService.findAbsentOrBreachLibraryStudent(subject.getExamId(),
+                    subject.getCode());
             if (list != null) {
                 for (ExamStudent student : list) {
                     try {
@@ -163,14 +172,15 @@ public class MarkCronService {
             }
             // 处理正常考生
             // 生成试评任务
-            List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(), subject.getCode(), MarkStatus.TRIAL);
+            List<MarkGroup> groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(), subject.getCode(),
+                    MarkStatus.TRIAL);
             for (MarkGroup group : groups) {
                 buildTrialLibrary(campusMap, group);
             }
             // 生成正评任务
             // 试评状态的分组也提前生成
-            groups = groupService
-                    .findByExamAndSubjectAndStatus(subject.getExamId(), subject.getCode(), MarkStatus.FORMAL, MarkStatus.TRIAL);
+            groups = groupService.findByExamAndSubjectAndStatus(subject.getExamId(), subject.getCode(),
+                    MarkStatus.FORMAL, MarkStatus.TRIAL);
             for (MarkGroup group : groups) {
                 buildFormalLibrary(subject, campusMap, group);
             }
@@ -180,8 +190,8 @@ public class MarkCronService {
     private void buildFormalLibrary(ExamSubject subject, Map<String, Campus> campusMap, MarkGroup group) {
         Date lastBuildTime = group.getBuildTime();
         int count = 0;
-        ExamStudent student = studentService
-                .findUnLibraryStudent(group.getExamId(), group.getSubjectCode(), group.getNumber(), lastBuildTime);
+        ExamStudent student = studentService.findUnLibraryStudent(group.getExamId(), group.getSubjectCode(),
+                group.getNumber(), lastBuildTime);
         while (student != null) {
             // 补充学习中心集合
             Campus campus = getCampus(student, campusMap);
@@ -194,8 +204,8 @@ public class MarkCronService {
                 lockService.watch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
                 lockService.watch(LockType.STUDENT, student.getId());
                 // 上锁后重复验证分组状态
-                if (!groupService.validateStatus(group.getExamId(), group.getSubjectCode(), group.getNumber(), MarkStatus.FORMAL,
-                        MarkStatus.TRIAL)) {
+                if (!groupService.validateStatus(group.getExamId(), group.getSubjectCode(), group.getNumber(),
+                        MarkStatus.FORMAL, MarkStatus.TRIAL)) {
                     break;
                 }
                 // 上锁后重复验证考生状态
@@ -205,13 +215,16 @@ public class MarkCronService {
                     count++;
                 }
             } catch (Exception e) {
-                log.error("build formal library error for studentId=" + student.getId() + ", groupNumber=" + group.getNumber(), e);
+                log.error(
+                        "build formal library error for studentId=" + student.getId() + ", groupNumber="
+                                + group.getNumber(), e);
             } finally {
                 lockService.unwatch(LockType.STUDENT, student.getId());
                 lockService.unwatch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
             }
             // 取下一个考生
-            student = studentService.findUnLibraryStudent(group.getExamId(), group.getSubjectCode(), group.getNumber(), lastBuildTime);
+            student = studentService.findUnLibraryStudent(group.getExamId(), group.getSubjectCode(), group.getNumber(),
+                    lastBuildTime);
         }
         // 有新任务创建,同时正评状态,此时才需要更新任务数量
         if (count > 0) {
@@ -226,7 +239,8 @@ public class MarkCronService {
     private void buildTrialLibrary(Map<String, Campus> campusMap, MarkGroup group) {
         while (group.getTrialCount() > group.getLibraryCount()) {
             // 随机取一个未生成试评任务的考生
-            ExamStudent student = studentService.randomUnTrialStudent(group.getExamId(), group.getSubjectCode(), group.getNumber());
+            ExamStudent student = studentService.randomUnTrialStudent(group.getExamId(), group.getSubjectCode(),
+                    group.getNumber());
             if (student == null) {
                 return;
             }
@@ -241,7 +255,8 @@ public class MarkCronService {
                 lockService.watch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());
                 lockService.watch(LockType.STUDENT, student.getId());
                 // 上锁后重新验证分组状态
-                if (!groupService.validateStatus(group.getExamId(), group.getSubjectCode(), group.getNumber(), MarkStatus.TRIAL)) {
+                if (!groupService.validateStatus(group.getExamId(), group.getSubjectCode(), group.getNumber(),
+                        MarkStatus.TRIAL)) {
                     break;
                 }
                 // 上锁后重复验证考生状态
@@ -251,7 +266,9 @@ public class MarkCronService {
                     markService.updateMarkedCount(group);
                 }
             } catch (Exception e) {
-                log.error("build trial library error for studentId=" + student.getId() + ", groupNumber=" + group.getNumber(), e);
+                log.error(
+                        "build trial library error for studentId=" + student.getId() + ", groupNumber="
+                                + group.getNumber(), e);
             } finally {
                 lockService.unwatch(LockType.STUDENT, student.getId());
                 lockService.unwatch(LockType.GROUP, group.getExamId(), group.getSubjectCode(), group.getNumber());

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

@@ -1,15 +1,17 @@
 package cn.com.qmth.stmms.biz.mark.service.Impl;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -41,6 +43,7 @@ import cn.com.qmth.stmms.biz.mark.model.MarkTrack;
 import cn.com.qmth.stmms.biz.mark.model.ProblemHistory;
 import cn.com.qmth.stmms.biz.mark.model.TrialHistory;
 import cn.com.qmth.stmms.biz.mark.model.TrialLibrary;
+import cn.com.qmth.stmms.biz.mark.query.MarkLibrarySearchQuery;
 import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.biz.mark.service.ProblemHistoryService;
 import cn.com.qmth.stmms.biz.utils.FormalTaskUtil;
@@ -54,9 +57,8 @@ import cn.com.qmth.stmms.common.enums.ThirdPolicy;
 
 /**
  * 与评卷相关的所有修改操作(非传播性的新增操作除外),全部汇总到这里进行集中控制
- * 
- * @author luoshi
  *
+ * @author luoshi
  */
 @Service("markService")
 public class MarkServiceImpl implements MarkService {
@@ -111,11 +113,14 @@ public class MarkServiceImpl implements MarkService {
     @Autowired
     private ProblemHistoryDao problemHistoryDao;
 
+    private Map<Integer, Long> markerLastUpdateTime = new ConcurrentHashMap<>();
+
     /**
-     * 某个大题已申请的评卷任务数量
-     * 
+     * 某个评卷分组已申请的评卷任务数量
+     *
      * @param group
-     * @return
+     *            - 评卷分组
+     * @return int
      */
     @Override
     public int applyCount(MarkGroup group) {
@@ -130,10 +135,10 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 某个评卷员已申请的评卷任务数量
-     * 
+     *
      * @param marker
-     * @param status
-     * @return
+     *            - 评卷员
+     * @return int
      */
     @Override
     public int applyCount(Marker marker) {
@@ -153,10 +158,10 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 某个评卷员已完成的评卷任务数量
-     * 
+     *
      * @param marker
-     * @param status
-     * @return
+     *            - 评卷员
+     * @return long
      */
     @Override
     public long markedCount(Marker marker) {
@@ -176,9 +181,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 释放某个大题的锁定任务
-     * 
+     * 释放某个评卷分组的锁定任务
+     *
      * @param group
+     *            - 评卷分组
      */
     @Override
     public void releaseByGroup(MarkGroup group) {
@@ -190,9 +196,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 重置某个大题的所有评卷任务
-     * 
+     * 重置某个评卷分组的所有评卷任务
+     *
      * @param group
+     *            - 评卷分组
      */
     @Override
     @Transactional
@@ -201,9 +208,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 删除某个大题
-     * 
+     * 删除某个评卷分组
+     *
      * @param group
+     *            - 评卷分组
      */
     @Override
     @Transactional
@@ -243,9 +251,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 修改某个大题给分步骤,并重置评卷任务
-     * 
+     * 修改某个评卷分组给分步骤,并重置评卷任务
+     *
      * @param group
+     *            - 评卷分组
      */
     @Override
     @Transactional
@@ -268,9 +277,11 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 评卷员申请领取某个正式评卷任务
-     * 
+     *
      * @param library
+     *            - 正评任务
      * @param marker
+     *            - 评卷员
      */
     @Override
     public boolean applyLibrary(MarkLibrary library, Marker marker) {
@@ -285,9 +296,12 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 评卷员申请领取某个试评评卷任务
-     * 
+     *
      * @param library
+     *            - 试评任务
      * @param marker
+     *            - 评卷员
+     * @return boolean
      */
     @Override
     public boolean applyLibrary(TrialLibrary library, Marker marker) {
@@ -296,10 +310,12 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 评卷员是否已领取了某个正式评卷任务
-     * 
+     *
      * @param library
+     *            - 正评任务
      * @param marker
-     * @return
+     *            - 评卷员
+     * @return boolean
      */
     @Override
     public boolean hasApplied(MarkLibrary library, Marker marker) {
@@ -308,10 +324,12 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 评卷员是否已领取了某个试评评卷任务
-     * 
+     *
      * @param library
+     *            - 试评任务
      * @param marker
-     * @return
+     *            - 评卷员
+     * @return boolean
      */
     @Override
     public boolean hasApplied(TrialLibrary library, Marker marker) {
@@ -320,9 +338,11 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 释放某个评卷员已领取的正评任务
-     * 
+     *
      * @param library
+     *            - 正评任务
      * @param marker
+     *            - 评卷员
      */
     @Override
     public void releaseLibrary(MarkLibrary library, Marker marker) {
@@ -331,9 +351,11 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 释放某个评卷员已领取的试评任务
-     * 
+     *
      * @param library
+     *            - 试评任务
      * @param marker
+     *            - 评卷员
      */
     @Override
     public void releaseLibrary(TrialLibrary library, Marker marker) {
@@ -342,9 +364,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 释放某个评卷员的所有锁定任务
-     * 
+     *
      * @param marker
-     * @param group
+     *            - 评卷员
      */
     @Override
     public void releaseByMarker(Marker marker) {
@@ -354,8 +376,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 重置某个评卷员
-     * 
+     *
      * @param marker
+     *            - 评卷员
      */
     @Override
     @Transactional
@@ -383,9 +406,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 根据考生ID删除评卷任务
-     * 
-     * @param studentId
+     * 根据考生删除评卷任务
+     *
+     * @param student
+     *            - 考生
      */
     @Override
     @Transactional
@@ -407,15 +431,17 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 评卷员提交评卷任务
-     * 
-     * @param task
+     *
+     * @param result
+     *            - 评卷结果
      * @param marker
-     * @return
+     *            - 评卷员
+     * @return boolean
      */
     @Override
     @Transactional
     public boolean submitTask(MarkResult result, Marker marker) {
-        // 判断大题是否存在/评卷是否结束
+        // 判断评卷分组是否存在/评卷是否结束
         MarkGroup group = groupDao.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
         if (group == null || group.getStatus() == MarkStatus.FINISH
                 || group.getStatus().getValue() != result.getStatusValue()) {
@@ -425,22 +451,12 @@ public class MarkServiceImpl implements MarkService {
         // 根据评卷状态选择读取不同的评卷任务
         if (group.getStatus() == MarkStatus.FORMAL) {
             MarkLibrary library = libraryDao.findOne(result.getLibraryId());
-            if (result.isProblem()) {
-                ProblemHistory history = new ProblemHistory();
-                history.setCreateTime(new Date());
-                history.setExamId(library.getExamId());
-                history.setExamNumber(library.getExamNumber());
-                history.setStudentId(library.getStudentId());
-                history.setLibraryId(library.getId());
-                history.setSubjectCode(library.getSubjectCode());
-                history.setGroupNumber(library.getGroupNumber());
-                history.setProblemId(result.getReason());
-                history.setStatus(HistoryStatus.WAITING);
-                problemHistoryService.save(history);
+            if (library != null && result.isProblem()) {
                 // 状态更新
                 Date now = new Date();
                 if (libraryDao.updateProblemResult(library.getId(), LibraryStatus.PROBLEM, marker.getId(), now,
-                        result.getSpent(), LibraryStatus.WAITING, LibraryStatus.MARKED, LibraryStatus.INSPECTED) == 0) {
+                        result.getSpent(), LibraryStatus.WAITING, LibraryStatus.MARKED, LibraryStatus.INSPECTED) != 0) {
+                    saveProblemHistory(result, library);
                     updateMarkedCount(group);
                     releaseLibrary(library, marker);
                     return true;
@@ -491,13 +507,34 @@ public class MarkServiceImpl implements MarkService {
         return false;
     }
 
+    private void saveProblemHistory(MarkResult result, MarkLibrary library) {
+        ProblemHistory history = problemHistoryDao.findByLibraryIdAndStatus(library.getId(), HistoryStatus.WAITING);
+        if (history == null) {
+            history = new ProblemHistory();
+        }
+        history.setCreateTime(new Date());
+        history.setExamId(library.getExamId());
+        history.setExamNumber(library.getExamNumber());
+        history.setStudentId(library.getStudentId());
+        history.setLibraryId(library.getId());
+        history.setSubjectCode(library.getSubjectCode());
+        history.setGroupNumber(library.getGroupNumber());
+        history.setProblemId(result.getReason());
+        history.setStatus(HistoryStatus.WAITING);
+        problemHistoryService.save(history);
+    }
+
     /**
      * 评卷员提交某个正评任务
-     * 
+     *
      * @param library
+     *            - 正评任务
      * @param marker
-     * @param trackMap
-     * @param tagList
+     *            - 评卷员
+     * @param group
+     *            - 评卷分组
+     * @param result
+     *            - 评卷结果
      */
     private boolean submitLibrary(MarkLibrary library, Marker marker, MarkGroup group, MarkResult result) {
         // 非本人领取的待评任务
@@ -627,8 +664,9 @@ public class MarkServiceImpl implements MarkService {
     /**
      * 管理员/组长打回某个评卷任务<br>
      * 暂时不用到BACKED状态,直接等同于重置该评卷任务
-     * 
+     *
      * @param library
+     *            - 正评任务
      */
     @Override
     @Transactional
@@ -642,7 +680,7 @@ public class MarkServiceImpl implements MarkService {
             trackDao.deleteByLibraryId(library.getId());
             specialTagDao.deleteByLibraryId(library.getId());
             updateMarkedCount(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
-            problemHistoryService.resetByStudentId(library.getStudentId(), userId);
+            problemHistoryService.resetByLibraryId(library.getId(), userId);
             return true;
         } else {
             return false;
@@ -651,8 +689,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 管理员/组长重置某个试评任务
-     * 
+     *
      * @param library
+     *            - 试评任务
      */
     @Override
     @Transactional
@@ -669,8 +708,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 管理员/组长处理仲裁卷
-     * 
+     *
      * @param history
+     *            - 仲裁卷
      */
     @Override
     @Transactional
@@ -687,10 +727,11 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 对某个考生某个科目进行主观题统分
-     * 
-     * @param examId
-     * @param subjectCode
-     * @param studentId
+     *
+     * @param student
+     *            - 考生
+     * @param groups
+     *            - 所有评卷分组
      */
     @Override
     @Transactional
@@ -698,9 +739,9 @@ public class MarkServiceImpl implements MarkService {
         if (student == null || groups == null || groups.isEmpty()) {
             return;
         }
-        List<ScoreItem> scoreList = new ArrayList<ScoreItem>();
+        List<ScoreItem> scoreList = new ArrayList<>();
         double totalScore = 0.0;
-        // 循环所有大题
+        // 循环所有评卷分组
         for (MarkGroup group : groups) {
             if (group.getStatus().equals(MarkStatus.TRIAL)) {
                 return;
@@ -722,7 +763,7 @@ public class MarkServiceImpl implements MarkService {
 
     private boolean calculateGroup(MarkGroup group, Integer studentId) {
         double score = 0;
-        List<ScoreItem> detail = new ArrayList<ScoreItem>();
+        List<ScoreItem> detail = new ArrayList<>();
         int count = 0;
         // 未设置算分策略的情况下,默认取平均分
         ScorePolicy policy = group.getScorePolicy() != null ? group.getScorePolicy() : ScorePolicy.AVG;
@@ -742,7 +783,7 @@ public class MarkServiceImpl implements MarkService {
             // Collections.sort(list,
             // Comparator.comparing(MarkLibrary::getMarkerScore));
             // 从小到大排序
-            Collections.sort(list, new Comparator<MarkLibrary>() {
+            list.sort(new Comparator<MarkLibrary>() {
 
                 @Override
                 public int compare(MarkLibrary o1, MarkLibrary o2) {
@@ -810,8 +851,7 @@ public class MarkServiceImpl implements MarkService {
         // 取平均分策略下,累计分数需要重新计算一次
         if (policy == ScorePolicy.AVG && count > 1) {
             score = score / count;
-            for (int i = 0; i < detail.size(); i++) {
-                ScoreItem item = detail.get(i);
+            for (ScoreItem item : detail) {
                 item.setScore(item.getScore() / count);
             }
         }
@@ -828,9 +868,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 更新某个大题已评任务数量
-     * 
+     * 更新某个评卷分组已评任务数量
+     *
      * @param group
+     *            - 评卷分组
      */
     @Override
     @Transactional
@@ -846,9 +887,10 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 更新某个大题评卷任务总量
-     * 
+     * 更新某个评卷分组评卷任务总量
+     *
      * @param group
+     *            - 评卷分组
      */
     @Override
     @Transactional
@@ -867,11 +909,12 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 更新某个科目所有大题评卷任务数量
-     * 
+     * 更新某个科目所有评卷分组评卷任务数量
+     *
      * @param examId
+     *            - 考试ID
      * @param subjectCode
-     * @param groupNumber
+     *            - 科目代码
      */
     @Transactional
     @Override
@@ -884,12 +927,16 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 根据考生、学习中心、大题构造正式评卷任务
-     * 
+     * 根据考生、学习中心、评卷分组构造正式评卷任务
+     *
      * @param student
+     *            - 考生
      * @param campus
+     *            - 学习中心
      * @param group
+     *            - 评卷分组
      * @param subject
+     *            - 科目
      */
     @Override
     @Transactional
@@ -906,6 +953,7 @@ public class MarkServiceImpl implements MarkService {
             library.setTaskNumber(1);
             library.setStatus(LibraryStatus.WAITING);
             libraryDao.save(library);
+            group.setLibraryCount(group.getLibraryCount() + 1);
             // 开启双评时需要判断是否生成第二份评卷任务
             if (group.getDoubleRate() != null && group.getDoubleRate() > 0) {
                 boolean needDouble = false;
@@ -918,9 +966,10 @@ public class MarkServiceImpl implements MarkService {
                             group.getExamId(), group.getSubjectCode(), group.getNumber(), 2);
                     int expectCount = (int) (studentCount * group.getDoubleRate());
                     // 随机数判断加入当前已经生成双评任务的比例加权
+                    // 实际双评任务数小于理论生成数 &&(剩余未生成双评的考生数量小于剩余应生成的数量||随机比例)
                     needDouble = doubleCount < expectCount
                             && ((studentCount - libraryCount + doubleCount) <= (expectCount - doubleCount) || Math
-                                    .random() < group.getDoubleRate());
+                                    .random() < group.getDoubleRate() + 0.1);
                 }
                 if (needDouble) {
                     library = new MarkLibrary();
@@ -933,6 +982,8 @@ public class MarkServiceImpl implements MarkService {
                     library.setTaskNumber(2);
                     library.setStatus(LibraryStatus.WAITING);
                     libraryDao.save(library);
+                    group.setLibraryCount(group.getLibraryCount() + 1);
+
                 }
             }
         }
@@ -941,11 +992,14 @@ public class MarkServiceImpl implements MarkService {
     }
 
     /**
-     * 根据考生、学习中心、大题构造试评评卷任务
-     * 
+     * 根据考生、学习中心、评卷分组构造试评评卷任务
+     *
      * @param student
+     *            - 考生
      * @param campus
+     *            - 学习中心
      * @param group
+     *            - 评卷分组
      */
     @Override
     @Transactional
@@ -966,9 +1020,9 @@ public class MarkServiceImpl implements MarkService {
     /**
      * 领取正式评卷任务时,用来区分的唯一标识<br/>
      * 多评时同一个考生的多份任务不能被同一位评卷员领取
-     * 
+     *
      * @param library
-     * @return
+     *            - 正评任务
      */
     private String getApplyTaskId(MarkLibrary library) {
         return library.getStudentId() + "_" + library.getGroupNumber();
@@ -976,9 +1030,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 领取试评评卷任务时,用来区分的唯一标识
-     * 
+     *
      * @param library
-     * @return
+     *            - 试评任务
      */
     private String getApplyTaskId(TrialLibrary library, Marker marker) {
         return library.getId() + "_" + marker.getId();
@@ -986,8 +1040,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 重置评卷分组的连带操作
-     * 
+     *
      * @param group
+     *            - 评卷分组
      */
     private void resetGroup(MarkGroup group) {
         if (group.getStatus() == MarkStatus.FORMAL) {
@@ -1018,8 +1073,9 @@ public class MarkServiceImpl implements MarkService {
 
     /**
      * 计算并更新指定评卷员的评卷质量指标
-     * 
+     *
      * @param marker
+     *            - 评卷员
      */
     @Override
     @Transactional
@@ -1028,10 +1084,10 @@ public class MarkServiceImpl implements MarkService {
         int finishCount = 0;
         int validCount = 0;
         double sumScore = 0;
-        double tempScore = 0;
+        double sumScore2 = 0;
         double avgScore = 0;
         double stdevScore = 0;
-        int sumSpent = 0;
+        double sumSpent = 0;
         double avgSpent = 0;
         for (MarkLibrary library : list) {
             finishCount++;
@@ -1042,24 +1098,45 @@ public class MarkServiceImpl implements MarkService {
             int spent = library.getMarkerSpent() != null ? library.getMarkerSpent() : 0;
 
             sumScore += score;
+            sumScore2 += Math.pow(score, 2);
             sumSpent += spent;
             if (finishCount > 0) {
                 avgScore = sumScore / finishCount;
                 avgSpent = sumSpent / finishCount;
                 // 递归法计算标准差
-                if (finishCount - 1 != 0) {
-                    tempScore = tempScore + 1.0 * (finishCount - 1) / finishCount * (score - avgScore)
-                            * (score - avgScore);
-                    stdevScore = Math.sqrt(tempScore / (finishCount - 1));
-                }
+                stdevScore = Math.sqrt(sumScore2 / finishCount - Math.pow(sumScore / finishCount, 2));
             }
         }
         markerDao.updateQualityById(marker.getId(), finishCount, validCount, avgSpent / 1000, avgScore, stdevScore);
+        markerLastUpdateTime.put(marker.getId(), System.currentTimeMillis());
     }
 
     private double sumTotalScore(Integer examId, String subjectCode) {
         Double score = groupDao.sumTotalScore(examId, subjectCode);
-        return score != null ? score.doubleValue() : 0d;
+        return score != null ? score : 0d;
+    }
+
+    @Override
+    public boolean needUpdateQuality(Marker marker, long expireMinutes) {
+        if (marker == null || marker.getLastLoginTime() == null) {
+            return false;
+        }
+        Long lastUpdateTime = markerLastUpdateTime.get(marker.getId());
+        MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
+        query.setPageNumber(1);
+        query.setPageSize(1);
+        query.setSort(new Sort(Sort.Direction.DESC, "markerTime"));
+        List<MarkLibrary> list = libraryDao.findByMarkerIdAndStatus(marker.getId(), LibraryStatus.MARKED, query);
+        MarkLibrary library = list.isEmpty() ? null : list.get(0);
+        if (library != null && library.getMarkerTime() != null) {
+            long lastMarkTime = library.getMarkerTime().getTime();
+            if (lastUpdateTime != null && lastUpdateTime > lastMarkTime) {
+                return false;
+            } else {
+                return (System.currentTimeMillis() - lastMarkTime) < (expireMinutes * 60 * 1000);
+            }
+        }
+        return false;
     }
 
 }

+ 5 - 2
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/Impl/ProblemHistoryServiceImpl.java

@@ -58,8 +58,8 @@ public class ProblemHistoryServiceImpl extends BaseQueryService<ProblemHistory>
 
     @Transactional
     @Override
-    public boolean resetByStudentId(Integer studentId, Integer userId) {
-        ProblemHistory history = historyDao.findByStudentIdAndStatus(studentId, HistoryStatus.WAITING);
+    public boolean resetByLibraryId(Integer libraryId, Integer userId) {
+        ProblemHistory history = historyDao.findByLibraryIdAndStatus(libraryId, HistoryStatus.WAITING);
         if (history != null) {
             history.setUserId(userId);
             history.setUpdateTime(new Date());
@@ -99,6 +99,9 @@ public class ProblemHistoryServiceImpl extends BaseQueryService<ProblemHistory>
                 if (query.getUserId() > 0) {
                     predicates.add(cb.equal(root.get("userId"), query.getUserId()));
                 }
+                if (query.getGroupNumber() > 0) {
+                    predicates.add(cb.equal(root.get("groupNumber"), query.getGroupNumber()));
+                }
                 if (query.getStatus() != null) {
                     predicates.add(cb.equal(root.get("status").as(HistoryStatus.class), query.getStatus()));
                 }

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

@@ -16,6 +16,7 @@ import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.exam.service.MarkerService;
@@ -81,6 +82,9 @@ public class TaskServiceImpl implements TaskService {
     @Autowired
     private TrialService trialService;
 
+    @Autowired
+    private ExamService examService;
+
     @Override
     public List<Task> findByQuery(MarkLibrarySearchQuery query) {
         List<Task> list = new LinkedList<Task>();
@@ -109,6 +113,8 @@ public class TaskServiceImpl implements TaskService {
         task.setPictureConfig(group.getPictureConfigList());
         task.setSheetUrls(PictureUrlBuilder.getSheetUrls(student.getExamId(), campusId, student.getSubjectCode(),
                 student.getExamNumber(), student.getSheetCount()));
+        task.setJsonUrl(PictureUrlBuilder.getAnswerJson(student.getExamId(), student.getSubjectCode(),
+                student.getPaperType(), student.getExamNumber()));
         task.setAnswerUrl(PictureUrlBuilder.getAnswerUrl(student.getExamId(), student.getSubjectCode()));
         task.setPaperUrl(PictureUrlBuilder.getPaperUrl(student.getExamId(), student.getSubjectCode()));
         task.setObjectiveScore(student.getObjectiveScore() != null ? student.getObjectiveScore() : 0);
@@ -148,6 +154,8 @@ public class TaskServiceImpl implements TaskService {
         task.setPictureConfig(group.getPictureConfigList());
         task.setSheetUrls(PictureUrlBuilder.getSheetUrls(library.getExamId(), library.getCampusId(),
                 library.getSubjectCode(), library.getExamNumber(), student.getSheetCount()));
+        task.setJsonUrl(PictureUrlBuilder.getAnswerJson(library.getExamId(), library.getSubjectCode(),
+                student.getPaperType(), student.getExamNumber()));
         task.setAnswerUrl(PictureUrlBuilder.getAnswerUrl(library.getExamId(), library.getSubjectCode()));
         task.setPaperUrl(PictureUrlBuilder.getPaperUrl(library.getExamId(), library.getSubjectCode()));
         task.setObjectiveScore(student != null ? student.getObjectiveScore() : 0);
@@ -186,6 +194,8 @@ public class TaskServiceImpl implements TaskService {
                 library.getSubjectCode(), library.getExamNumber(), student.getSheetCount()));
         task.setAnswerUrl(PictureUrlBuilder.getAnswerUrl(library.getExamId(), library.getSubjectCode()));
         task.setPaperUrl(PictureUrlBuilder.getPaperUrl(library.getExamId(), library.getSubjectCode()));
+        task.setJsonUrl(PictureUrlBuilder.getAnswerJson(library.getExamId(), library.getSubjectCode(),
+                student.getPaperType(), student.getExamNumber()));
         task.setObjectiveScore(student != null ? student.getObjectiveScore() : 0);
         if (history != null) {
             task.setMarkTime(history.getMarkerTime());

+ 36 - 27
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/mark/service/MarkService.java

@@ -19,7 +19,7 @@ public interface MarkService {
 
     /**
      * 释放某个评卷员已领取的正评任务
-     * 
+     *
      * @param library
      * @param marker
      */
@@ -27,7 +27,7 @@ public interface MarkService {
 
     /**
      * 释放某个评卷员已领取的试评任务
-     * 
+     *
      * @param library
      * @param marker
      */
@@ -35,28 +35,28 @@ public interface MarkService {
 
     /**
      * /** 释放某个大题的锁定任务
-     * 
+     *
      * @param group
      */
     void releaseByGroup(MarkGroup group);
 
     /**
      * 重置某个大题的所有评卷任务
-     * 
+     *
      * @param group
      */
     void resetByGroup(MarkGroup group);
 
     /**
      * 删除某个大题
-     * 
+     *
      * @param group
      */
     void deleteGroup(MarkGroup group);
 
     /**
      * 修改并重置某个大题
-     * 
+     *
      * @param group
      * @param third
      */
@@ -64,7 +64,7 @@ public interface MarkService {
 
     /**
      * 评卷员申请领取某个正式评卷任务
-     * 
+     *
      * @param library
      * @param marker
      */
@@ -72,7 +72,7 @@ public interface MarkService {
 
     /**
      * 评卷员申请领取某个试评评卷任务
-     * 
+     *
      * @param library
      * @param marker
      * @return
@@ -81,7 +81,7 @@ public interface MarkService {
 
     /**
      * 评卷员是否已领取某个正式评卷任务
-     * 
+     *
      * @param library
      * @param marker
      * @return
@@ -90,7 +90,7 @@ public interface MarkService {
 
     /**
      * 评卷员是否已领取某个试评评卷任务
-     * 
+     *
      * @param library
      * @param marker
      * @return
@@ -99,7 +99,7 @@ public interface MarkService {
 
     /**
      * 释放某个评卷员的所有锁定任务
-     * 
+     *
      * @param marker
      * @param group
      */
@@ -107,21 +107,21 @@ public interface MarkService {
 
     /**
      * 重置某个评卷员
-     * 
+     *
      * @param marker
      */
     void resetMarker(Marker marker);
 
     /**
      * 根据考生删除评卷任务
-     * 
+     *
      * @param student
      */
     void deleteByStudent(ExamStudent student);
 
     /**
      * 对某个考生某个科目进行主观题统分
-     * 
+     *
      * @param student
      * @param groups
      */
@@ -129,14 +129,14 @@ public interface MarkService {
 
     /**
      * 管理员/组长处理仲裁卷
-     * 
+     *
      * @param history
      */
     void processArbitrate(ArbitrateHistory history);
 
     /**
      * 管理员/组长打回某个评卷任务
-     * 
+     *
      * @param library
      * @return
      */
@@ -144,21 +144,21 @@ public interface MarkService {
 
     /**
      * 更新某个大题已评任务数量
-     * 
+     *
      * @param group
      */
     void updateMarkedCount(MarkGroup group);
 
     /**
      * 更新某个大题评卷任务总量
-     * 
+     *
      * @param group
      */
     void updateLibraryCount(MarkGroup group);
 
     /**
      * 更新某个科目所有大题评卷任务总量与已评数量
-     * 
+     *
      * @param examId
      * @param subjectCode
      */
@@ -166,7 +166,7 @@ public interface MarkService {
 
     /**
      * 某个大题已申请的评卷任务数量
-     * 
+     *
      * @param group
      * @return
      */
@@ -174,7 +174,7 @@ public interface MarkService {
 
     /**
      * 某个评卷员已申请的评卷任务数量
-     * 
+     *
      * @param marker
      * @return
      */
@@ -182,7 +182,7 @@ public interface MarkService {
 
     /**
      * 根据考生、学习中心、大题构造正式评卷任务
-     * 
+     *
      * @param student
      * @param campus
      * @param group
@@ -192,7 +192,7 @@ public interface MarkService {
 
     /**
      * 根据考生、学习中心、大题构造试评评卷任务
-     * 
+     *
      * @param student
      * @param campus
      * @param group
@@ -201,7 +201,7 @@ public interface MarkService {
 
     /**
      * 评卷员提交评卷任务
-     * 
+     *
      * @param task
      * @param marker
      * @return
@@ -210,7 +210,7 @@ public interface MarkService {
 
     /**
      * 管理员/组长重置某个试评任务
-     * 
+     *
      * @param library
      * @return
      */
@@ -218,7 +218,7 @@ public interface MarkService {
 
     /**
      * 某个评卷员已完成的评卷任务数量
-     * 
+     *
      * @param marker
      * @return
      */
@@ -226,9 +226,18 @@ public interface MarkService {
 
     /**
      * 计算并更新指定评卷员的评卷质量指标
-     * 
+     *
      * @param marker
      */
     void updateQuality(Marker marker);
 
+    /**
+     * 判断评卷员是否需要更新评卷质量
+     *
+     * @param marker
+     * @param expireMinutes
+     * @return
+     */
+    boolean needUpdateQuality(Marker marker, long expireMinutes);
+
 }

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

@@ -15,6 +15,6 @@ public interface ProblemHistoryService {
 
     ProblemHistorySearchQuery findByQuery(ProblemHistorySearchQuery query);
 
-    boolean resetByStudentId(Integer studentId, Integer userId);
+    boolean resetByLibraryId(Integer libraryId, Integer userId);
 
 }

+ 10 - 11
stmms-biz/src/main/java/cn/com/qmth/stmms/biz/report/utils/unit/BaseCalculatorUnit.java

@@ -13,13 +13,13 @@ public class BaseCalculatorUnit {
     public Double passScore;
 
     public int passCount;
-    
+
     public double passRate;
 
     public Double excellentScore;
 
     public int excellentCount;
-    
+
     public double excellentRate;
 
     public double aggScore;
@@ -47,9 +47,9 @@ public class BaseCalculatorUnit {
     public double coefficient;
 
     public double discrimination;
-    
+
     public double fullScoreRate;
-    
+
     public BaseCalculatorUnit() {
         minScore = Double.MAX_VALUE;
     }
@@ -82,17 +82,16 @@ public class BaseCalculatorUnit {
         minScore = Math.min(minScore, score);
         avgScore = sumScore / count;
         // 递归法计算标准差
-        tempSum = tempSum + 1.0 * (count - 1) / count * (score - avgScore) * (score - avgScore);
-        stdev = count==1?0:Math.sqrt(tempSum / (count - 1));
+        stdev = Math.sqrt(sumScore2 / count - Math.pow(sumScore / count, 2));
         // 难度
         difficulty = fullScore > 0 ? avgScore / fullScore : 0;
         // 差异系数
         coefficient = avgScore > 0 ? stdev * 100 / avgScore : 0;
         // 区分度
         double temp = Math.sqrt((sumScore2 - sumScore * sumScore / count) * (sumTotalScore2 - sumTotalScore * sumTotalScore / count));
-        if(temp != 0){
+        if (temp != 0) {
             discrimination = (aggScore - sumScore * sumTotalScore / count) / temp;
-        }else{
+        } else {
             discrimination = 0;
         }
         // 及格率
@@ -105,8 +104,8 @@ public class BaseCalculatorUnit {
 
     @Override
     public String toString() {
-        return "count=" + count + ",zeroCount=" + zeroCount + ",fullCount=" + fullCount + ",fullScore=" + fullScore
-                + ",maxScore=" + maxScore + ",minScore=" + minScore + ",avgScore=" + avgScore + ",stdev=" + stdev
-                + ",difficulty=" + difficulty + ",coefficient=" + coefficient + ",discrimination=" + discrimination;
+        return "count=" + count + ",zeroCount=" + zeroCount + ",fullCount=" + fullCount + ",fullScore=" + fullScore + ",maxScore="
+                + maxScore + ",minScore=" + minScore + ",avgScore=" + avgScore + ",stdev=" + stdev + ",difficulty=" + difficulty
+                + ",coefficient=" + coefficient + ",discrimination=" + discrimination;
     }
 }

+ 1 - 1
stmms-biz/src/main/resources/service-context.xml

@@ -65,4 +65,4 @@ http://www.springframework.org/schema/task/spring-task.xsd
     <task:executor id="cron-executor" pool-size="5"/>
     <task:scheduler id="cron-scheduler" pool-size="5"/>
     <task:annotation-driven executor="cron-executor" scheduler="cron-scheduler"/>
-</beans>
+</beans>

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

@@ -0,0 +1,34 @@
+package cn.com.qmth.stmms.common.enums;
+
+public enum ExamType {
+
+    SCAN_IMAGE("扫描图片类型", 1), MULTI_MEDIA("多媒体类型", 2);
+
+    private String name;
+
+    private int value;
+
+    private ExamType(String name, int value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getValue() {
+        return value;
+    }
+
+    public static ExamType findByValue(int value) {
+        ExamType status = null;
+        for (ExamType s : ExamType.values()) {
+            if (s.getValue() == value) {
+                status = s;
+                break;
+            }
+        }
+        return status;
+    }
+}

+ 1 - 1
stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/LockType.java

@@ -3,7 +3,7 @@ package cn.com.qmth.stmms.common.enums;
 public enum LockType {
     GROUP("group"), STUDENT("student"), MARKER("marker"), USER("user"), FORMAL_LIBRARY("formal_library"), TRIAL_LIBRARY(
             "trial_library"), ARBITRATE("arbitrate"), BATCH_QUALITY("batch_quality"), SAVE_SHEET("save_sheet"), SCORE_CALCULATE(
-            "score_calculate"), GROUP_LIBRARY("group_library");
+            "score_calculate"), GROUP_LIBRARY("group_library"), DATA_SYNC("data_sync");
 
     private String name;
 

+ 0 - 31
stmms-common/src/main/java/cn/com/qmth/stmms/common/enums/ProblemType.java

@@ -1,31 +0,0 @@
-package cn.com.qmth.stmms.common.enums;
-
-public enum ProblemType {
-    FUZZY("试卷模糊", 0), MISPLACED("答错位置", 1), UNIDENTIFIED("无法判别", 2);
-
-    private String name;
-
-    private int value;
-
-    private ProblemType(String name, int value) {
-        this.name = name;
-        this.value = value;
-    }
-
-    public String getName() {
-        return name;
-    }
-
-    public int getValue() {
-        return value;
-    }
-
-    public static ProblemType findByValue(int value) {
-        for (ProblemType c : ProblemType.values()) {
-            if (c.getValue() == value) {
-                return c;
-            }
-        }
-        return null;
-    }
-}

+ 16 - 4
stmms-common/src/main/java/cn/com/qmth/stmms/common/utils/PictureUrlBuilder.java

@@ -26,10 +26,16 @@ public class PictureUrlBuilder {
 
     private static final String PACKAGE_URL_TEMPLATE = "/{0}/{1}/{2}.{3}";
 
+    private static final String JSON_PAPER_TYPE_URL_TEMPLATE = "/{0}/{1}-{2}/{3}.{4}";
+
+    private static final String JSON_URL_TEMPLATE = "/{0}/{1}/{2}.{3}";
+
     private static final String DEFAULT_SUFFIX = "jpg";
 
     private static final String DOCUMENT_SUFFIX = "pdf";
 
+    private static final String JSON_SUFFIX = "json";
+
     public static List<String> getInnerSheetUrls(int examId, String examNumber, int count) {
         List<String> list = new LinkedList<String>();
         if (StringUtils.isNotBlank(examNumber) && count > 0) {
@@ -41,8 +47,7 @@ public class PictureUrlBuilder {
         return list;
     }
 
-    public static List<String> getSheetUrls(int examId, int campusId, String subjectCode, String examNumber,
-            int count) {
+    public static List<String> getSheetUrls(int examId, int campusId, String subjectCode, String examNumber, int count) {
         List<String> list = new LinkedList<String>();
         if (StringUtils.isNotEmpty(subjectCode) && count > 0) {
             for (int i = 1; i <= count; i++) {
@@ -63,8 +68,7 @@ public class PictureUrlBuilder {
                 examNumber, String.valueOf(index), DEFAULT_SUFFIX);
     }
 
-    public static List<String> getSliceUrls(int examId, int campusId, String subjectCode, String examNumber,
-            int count) {
+    public static List<String> getSliceUrls(int examId, int campusId, String subjectCode, String examNumber, int count) {
         List<String> list = new LinkedList<String>();
         if (StringUtils.isNotEmpty(subjectCode) && count > 0) {
             for (int i = 1; i <= count; i++) {
@@ -105,4 +109,12 @@ public class PictureUrlBuilder {
         }
         return list;
     }
+
+    public static String getAnswerJson(Integer examId, String subjectCode, String paperType, String examNumber) {
+        if (StringUtils.isNotEmpty(paperType)) {
+            return MessageFormat.format(JSON_PAPER_TYPE_URL_TEMPLATE, String.valueOf(examId), subjectCode, paperType,
+                    examNumber, JSON_SUFFIX);
+        }
+        return MessageFormat.format(JSON_URL_TEMPLATE, String.valueOf(examId), subjectCode, examNumber, JSON_SUFFIX);
+    }
 }

+ 0 - 1
stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/ObjectiveQuestionDTO.java

@@ -59,7 +59,6 @@ public class ObjectiveQuestionDTO implements QuestionDTO {
         question.setAnswer(StringUtils.trimToNull(answer));
         question.setTotalScore(totalScore);
         question.setIntervalScore(1d);
-        question.setTotalCount(0);
         return question;
     }
 

+ 3 - 4
stmms-web/src/main/java/cn/com/qmth/stmms/admin/dto/SubjectiveQuestionDTO.java

@@ -67,12 +67,12 @@ public class SubjectiveQuestionDTO implements QuestionDTO {
         setIntervalScore(question.getIntervalScore());
         setPicList(group != null ? group.getPicList() : "");
         setDoubleRate(group != null && group.getDoubleRate() != null ? group.getDoubleRate() : 0d);
-        setArbitrateThreshold(
-                group != null && group.getArbitrateThreshold() != null ? group.getArbitrateThreshold() : 0d);
+        setArbitrateThreshold(group != null && group.getArbitrateThreshold() != null ? group.getArbitrateThreshold()
+                : 0d);
         setScorePolicy(group != null && group.getScorePolicy() != null ? group.getScorePolicy().getValue()
                 : ScorePolicy.AVG.getValue());
         setMarkMode(group != null && group.getMarkMode() != null ? group.getMarkMode().getName() : "");
-        setTrialCount(group.getTrialCount());
+        setTrialCount(group != null&& group.getTrialCount() != null  ? group.getTrialCount() : 0);
     }
 
     public ExamQuestion transform() {
@@ -85,7 +85,6 @@ public class SubjectiveQuestionDTO implements QuestionDTO {
         question.setObjective(false);
         question.setTotalScore(totalScore);
         question.setIntervalScore(intervalScore != null && intervalScore > 0 ? intervalScore : 1d);
-        question.setTotalCount(0);
         question.setPicList(picList);
         question.setDoubleRate(doubleRate);
         question.setArbitrateThreshold(arbitrateThreshold);

+ 19 - 6
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/AnswerCheckController.java

@@ -7,6 +7,9 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
 import org.apache.commons.lang.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -18,14 +21,15 @@ import org.springframework.web.servlet.ModelAndView;
 
 import cn.com.qmth.stmms.biz.campus.model.Campus;
 import cn.com.qmth.stmms.biz.campus.service.CampusService;
+import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.query.ExamStudentSearchQuery;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.utils.PictureUrlBuilder;
-import net.sf.json.JSONArray;
-import net.sf.json.JSONObject;
 
 @Controller
 @RequestMapping("/admin/exam/check/answer")
@@ -40,6 +44,9 @@ public class AnswerCheckController extends BaseExamController {
     @Autowired
     private CampusService campusService;
 
+    @Autowired
+    private ExamService examService;
+
     @Value("${sheet.image.server}")
     private String sheetServer;
 
@@ -47,7 +54,11 @@ public class AnswerCheckController extends BaseExamController {
     public ModelAndView index(HttpServletRequest request) {
         int examId = getSessionExamId(request);
         if (examId > 0) {
+            Exam exam = examService.findById(examId);
             ModelAndView view = new ModelAndView("modules/exam/checkAnswer");
+            if (ExamType.MULTI_MEDIA.equals(exam.getType())) {
+                view = new ModelAndView("modules/exam/permission");
+            }
             return view;
         } else {
             return new ModelAndView("redirect:/admin/exam/list");
@@ -128,13 +139,15 @@ public class AnswerCheckController extends BaseExamController {
         result.accumulate("absent", student.isAbsent());
 
         Campus campus = campusService.findBySchoolAndName(student.getSchoolId(), student.getCampusName());
-        result.accumulate("sheetUrls", PictureUrlBuilder.getSheetUrls(student.getExamId(), campus.getId(),
-                student.getSubjectCode(), student.getExamNumber(), student.getSheetCount()));
+        result.accumulate(
+                "sheetUrls",
+                PictureUrlBuilder.getSheetUrls(student.getExamId(), campus.getId(), student.getSubjectCode(),
+                        student.getExamNumber(), student.getSheetCount()));
 
         JSONArray array = new JSONArray();
         Map<Integer, String> titles = new HashMap<Integer, String>();
-        List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndPaperType(student.getExamId(),
-                student.getSubjectCode(), true, student.getPaperType());
+        List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndPaperType(
+                student.getExamId(), student.getSubjectCode(), true, student.getPaperType());
         List<String> answers = student.getAnswerList();
         if (questions.isEmpty()) {
             // 未设置客观题或无客观题

+ 19 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ArbitrateController.java

@@ -27,9 +27,11 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
+import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.lock.LockService;
@@ -42,6 +44,7 @@ import cn.com.qmth.stmms.biz.mark.service.TaskService;
 import cn.com.qmth.stmms.biz.user.service.UserService;
 import cn.com.qmth.stmms.common.annotation.RoleRequire;
 import cn.com.qmth.stmms.common.domain.WebUser;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.enums.HistoryStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.Role;
@@ -77,6 +80,9 @@ public class ArbitrateController extends BaseExamController {
     @Autowired
     private ExamQuestionService questionService;
 
+    @Autowired
+    private ExamService examService;
+
     @Value("${slice.image.server}")
     private String sliceServer;
 
@@ -86,6 +92,9 @@ public class ArbitrateController extends BaseExamController {
     @Value("${card.server}")
     private String cardServer;
 
+    @Value("${json.server}")
+    private String jsonServer;
+
     // 并发处理互斥锁
     private Map<Integer, Integer> currentTaskMap = new HashMap<Integer, Integer>();
 
@@ -144,6 +153,11 @@ public class ArbitrateController extends BaseExamController {
         model.addAttribute("subject", subjectService.find(group.getExamId(), group.getSubjectCode()));
         model.addAttribute("group", group);
         model.addAttribute("history", history);
+        model.addAttribute("jsonServer", jsonServer);
+        Exam exam = examService.findById(examId);
+        if (ExamType.MULTI_MEDIA.equals(exam.getType())) {
+            return "modules/exam/arbitrateSingleProcessJson";
+        }
         return "modules/exam/arbitrateSingleProcess";
     }
 
@@ -166,6 +180,11 @@ public class ArbitrateController extends BaseExamController {
         model.addAttribute("cardServer", cardServer);
         model.addAttribute("subject", subjectService.find(group.getExamId(), group.getSubjectCode()));
         model.addAttribute("group", group);
+        model.addAttribute("jsonServer", jsonServer);
+        Exam exam = examService.findById(examId);
+        if (ExamType.MULTI_MEDIA.equals(exam.getType())) {
+            return "modules/exam/arbitrateBatchProcessJson";
+        }
         return "modules/exam/arbitrateBatchProcess";
     }
 

+ 187 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/DataSyncController.java

@@ -0,0 +1,187 @@
+package cn.com.qmth.stmms.admin.exam;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import cn.com.qmth.stmms.admin.dto.SubjectiveQuestionDTO;
+import cn.com.qmth.stmms.admin.thread.DataSyncThread;
+import cn.com.qmth.stmms.admin.utils.HttpUtil;
+import cn.com.qmth.stmms.admin.utils.SessionExamUtils;
+import cn.com.qmth.stmms.admin.utils.UpyunConfig;
+import cn.com.qmth.stmms.biz.exam.model.DataSync;
+import cn.com.qmth.stmms.biz.exam.service.DataSyncService;
+import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
+import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
+import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
+import cn.com.qmth.stmms.biz.lock.LockService;
+import cn.com.qmth.stmms.common.annotation.RoleRequire;
+import cn.com.qmth.stmms.common.enums.LockType;
+import cn.com.qmth.stmms.common.enums.Role;
+import cn.com.qmth.stmms.common.utils.ExportExcel;
+
+@Controller
+@RequestMapping("/admin/exam/data/sync")
+public class DataSyncController extends BaseExamController {
+
+    protected static Logger log = LoggerFactory.getLogger(DataSyncController.class);
+
+    @Autowired
+    private ExamSubjectService subjectService;
+
+    @Autowired
+    private ExamStudentService studentService;
+
+    @Autowired
+    private ExamQuestionService questionService;
+
+    @Qualifier("task-executor")
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @Autowired
+    private LockService lockService;
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private DataSyncService dataSyncService;
+
+    @Value("${qmth.examcloud.host}")
+    private String host;
+
+    @Value("${qmth.examcloud.port}")
+    private String port;
+
+    @Value("${qmth.examcloud.rootOrgId}")
+    private String rootOrgId;
+
+    @Value("${qmth.examcloud.appId}")
+    private String appId;
+
+    @Value("${qmth.examcloud.secretKey}")
+    private String secretKey;
+
+    @Value("${qmth.examcloud.subject.uri}")
+    private String subjectUri;
+
+    @Value("${qmth.examcloud.student.uri}")
+    private String studentUri;
+
+    @Value("${qmth.examcloud.pageSize}")
+    private Integer pageSize;
+
+    @Value("${file.root}")
+    private String baseDir;
+
+    @Autowired
+    private UpyunConfig config;
+
+    @RequestMapping()
+    public ModelAndView index(HttpServletRequest request, @RequestParam Integer examId) {
+        if (examId != null) {
+            SessionExamUtils.setExamId(request, examId);
+        }
+        examId = getSessionExamId(request);
+
+        DataSync dataSync = dataSyncService.findByExamId(examId);
+        if (dataSync == null) {
+            dataSync = new DataSync();
+            dataSync.setExamId(examId);
+            dataSync.setRootOrgId(rootOrgId);
+            dataSync.setStudentUrl(host + ":" + port + studentUri);
+            dataSync.setSubjectUrl(host + ":" + port + subjectUri);
+            dataSync.setAppId(appId);
+            dataSync.setSecretKey(secretKey);
+        }
+        ModelAndView view = new ModelAndView("modules/exam/dataSync");
+        view.addObject("running", lockService.isLocked(LockType.DATA_SYNC, examId));
+        view.addObject("exam", examService.findById(examId));
+        view.addObject("dataSync", dataSync);
+        return view;
+    }
+
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public String save(HttpServletRequest request, DataSync dataSync, RedirectAttributes redirectAttributes) {
+        int examId = getSessionExamId(request);
+        if (lockService.trylock(LockType.DATA_SYNC, examId)) {
+            DataSyncThread thread = new DataSyncThread(dataSync, pageSize, baseDir, config, lockService,
+                    dataSyncService, examService, studentService, subjectService);
+            taskExecutor.submit(thread);
+        }
+        dataSync = dataSyncService.findByExamId(examId);
+        if (lockService.isLocked(LockType.DATA_SYNC, examId) || (dataSync != null && dataSync.isFinished())) {
+            addMessage(redirectAttributes, "创建成功");
+        } else {
+            addMessage(redirectAttributes, "同步失败");
+        }
+        return "redirect:/admin/exam-list";
+    }
+
+    @RequestMapping(value = "/export", method = RequestMethod.GET)
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public ModelAndView exportFile(HttpServletRequest request, HttpServletResponse response,
+            RedirectAttributes redirectAttributes) {
+        int examId = getSessionExamId(request);
+        DataSync sync = dataSyncService.findByExamId(examId);
+        try {
+            List<SubjectiveQuestionDTO> list = getPaperStruct(sync);
+            new ExportExcel("主观题数据", SubjectiveQuestionDTO.class, 2).setDataList(list).write(response, "主观题数据.xlsx")
+                    .dispose();
+            return null;
+        } catch (Exception e) {
+            log.error("export sync subject:", e);
+            addMessage(redirectAttributes, "导出数据失败!失败信息:" + e.getMessage());
+        }
+        return new ModelAndView("redirect:/admin/exam/data/sync?examId=" + examId);
+    }
+
+    private List<SubjectiveQuestionDTO> getPaperStruct(DataSync sync) {
+        List<SubjectiveQuestionDTO> list = new ArrayList<SubjectiveQuestionDTO>();
+
+        HttpUtil subjectHttp = new HttpUtil(sync.getSubjectUrl(), sync.getSecretKey(), sync.getAppId(),
+                sync.getRootOrgId());
+        JSONObject datas = new JSONObject();
+        datas.accumulate("examId", sync.getCloudExamId());
+        String subjectResult = subjectHttp.httpAction(null, datas.toString());
+        JSONObject subjectJson = JSONObject.fromObject(subjectResult);
+        JSONArray subjectArray = subjectJson.getJSONArray("paperStructList");
+        for (int i = 0; i < subjectArray.size(); i++) {
+            JSONObject subject = subjectArray.getJSONObject(i);
+            JSONArray questionArray = subject.getJSONArray("questions");
+            for (int j = 0; j < questionArray.size(); j++) {
+                JSONObject question = questionArray.getJSONObject(j);
+                SubjectiveQuestionDTO questionDTO = new SubjectiveQuestionDTO();
+                questionDTO.setSubjectCode(subject.getString("subjectCode"));
+                questionDTO.setSubjectName(subject.getString("subjectName"));
+                questionDTO.setMainNumber(question.getInt("mainNumber"));
+                questionDTO.setTitle(question.getString("mainTitle"));
+                questionDTO.setSubNumber(question.getInt("subNumber"));
+                questionDTO.setTotalScore(question.getDouble("totalScore"));
+                list.add(questionDTO);
+            }
+        }
+        return list;
+    }
+}

+ 7 - 3
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ExamController.java

@@ -41,6 +41,7 @@ import cn.com.qmth.stmms.biz.user.model.User;
 import cn.com.qmth.stmms.common.annotation.RoleRequire;
 import cn.com.qmth.stmms.common.domain.WebUser;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.enums.Role;
 import cn.com.qmth.stmms.common.utils.Paginator;
 import cn.com.qmth.stmms.common.utils.PictureUrlBuilder;
@@ -90,6 +91,7 @@ public class ExamController extends BaseExamController {
     public String add(Exam exam, Model model) {
         model.addAttribute("exam", exam);
         model.addAttribute("statusList", ExamStatus.values());
+        model.addAttribute("typeList", ExamType.values());
         return "modules/exam/examForm";
     }
 
@@ -110,6 +112,9 @@ public class ExamController extends BaseExamController {
         exam.setSchoolId(user.getSchoolId());
         exam.setCreatorId(user.getId());
         exam.setStatus(ExamStatus.START);
+        if (exam.getType().equals(ExamType.MULTI_MEDIA)) {
+            exam.setForceSpecialTag(false);
+        }
         exam = examService.save(exam);
         addMessage(redirectAttributes, "创建考试'" + exam.getName() + "'成功");
         return "redirect:/admin/exam-list";
@@ -154,7 +159,6 @@ public class ExamController extends BaseExamController {
     @RequestMapping("/exam-view/{examId}")
     public String view(Model model, HttpServletRequest request, @PathVariable Integer examId) {
         SessionExamUtils.setExamId(request, examId);
-
         Exam exam = examService.findById(examId);
         long studentCount = examStudentService.countByExamId(exam.getId());
         long subjectCount = examSubjectService.count(examId);
@@ -240,8 +244,8 @@ public class ExamController extends BaseExamController {
             model.addAttribute("imageServer", imageServer);
             return "modules/mark/picConfig";
         } else {
-            addMessage(redirectAttributes, "参数有误");
-            return "redirect:/admin/exam-list";
+            model.addAttribute("message", "参数有误");
+            return "modules/mark/picConfig";
         }
     }
 

+ 2 - 1
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ImageCheckController.java

@@ -76,7 +76,8 @@ public class ImageCheckController extends BaseExamController {
     public ModelAndView checkImage(HttpServletRequest request) {
         int examId = getSessionExamId(request);
         if (running.compareAndSet(false, true) == true) {
-            ImageCheckThread thread = new ImageCheckThread(examId, running, config, baseDir, studentService, campusService);
+            ImageCheckThread thread = new ImageCheckThread(examId, running, config, baseDir, studentService,
+                    campusService);
             taskExecutor.submit(thread);
         }
         return new ModelAndView("redirect:/admin/exam/check/image");

+ 53 - 6
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/LibraryController.java

@@ -1,5 +1,6 @@
 package cn.com.qmth.stmms.admin.exam;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
@@ -9,6 +10,7 @@ import net.sf.json.JSONObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Controller;
 import org.springframework.ui.Model;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -16,9 +18,14 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
+import cn.com.qmth.stmms.biz.exam.model.Exam;
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
+import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.exam.service.MarkerService;
 import cn.com.qmth.stmms.biz.lock.LockService;
@@ -32,6 +39,7 @@ import cn.com.qmth.stmms.common.enums.LibraryStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkStatus;
 import cn.com.qmth.stmms.common.enums.Role;
+import cn.com.qmth.stmms.common.utils.PictureUrlBuilder;
 import cn.com.qmth.stmms.common.utils.RequestUtils;
 
 @Controller("libraryController")
@@ -46,6 +54,9 @@ public class LibraryController extends BaseExamController {
     @Autowired
     private MarkLibraryService libraryService;
 
+    @Autowired
+    private ExamStudentService studentService;
+
     @Autowired
     private MarkerService markerService;
 
@@ -58,6 +69,12 @@ public class LibraryController extends BaseExamController {
     @Autowired
     private ExamQuestionService questionService;
 
+    @Autowired
+    private ExamService examService;
+
+    @Value("${json.server}")
+    private String jsonServer;
+
     @RequestMapping
     public String list(Model model, HttpServletRequest request, MarkLibrarySearchQuery query,
             @RequestParam(required = false) LibraryStatus status) {
@@ -75,7 +92,8 @@ public class LibraryController extends BaseExamController {
             query.setSubjectCode(subjectList.get(0).getCode());
         }
         subjectFilter(query, wu);
-        List<MarkGroup> groupList = groupService.findByExamAndSubjectAndStatus(examId, query.getSubjectCode(), MarkStatus.FORMAL);
+        List<MarkGroup> groupList = groupService.findByExamAndSubjectAndStatus(examId, query.getSubjectCode(),
+                MarkStatus.FORMAL);
         // if (groupList.isEmpty()) {
         // return "redirect:/admin/exam/mark";
         // }
@@ -91,8 +109,8 @@ public class LibraryController extends BaseExamController {
                 }
             }
             for (MarkGroup group : groupList) {
-                group.setQuestionList(questionService
-                        .findByExamAndSubjectAndObjectiveAndGroupNumber(examId, group.getSubjectCode(), false, group.getNumber()));
+                group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                        group.getSubjectCode(), false, group.getNumber()));
             }
         }
         MarkLibrarySearchQuery query2 = new MarkLibrarySearchQuery();
@@ -109,6 +127,8 @@ public class LibraryController extends BaseExamController {
         model.addAttribute("markerList",
                 markerService.findByExamAndSubjectAndGroup(examId, query.getSubjectCode(), query.getGroupNumber()));
         model.addAttribute("inspectedCount", inspectedCount);
+        Exam exam = examService.findById(examId);
+        model.addAttribute("examType", exam.getType());
         return "modules/exam/libraryList";
     }
 
@@ -122,8 +142,11 @@ public class LibraryController extends BaseExamController {
         if (library != null) {
             if (subjectCheck(library.getSubjectCode(), RequestUtils.getWebUser(request))) {
                 try {
-                    lockService.watch(LockType.GROUP, library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
-                    if (library.getStatus().equals(LibraryStatus.MARKED) && markService.backLibrary(library, wu.getId())) {
+                    lockService.watch(LockType.GROUP, library.getExamId(), library.getSubjectCode(),
+                            library.getGroupNumber());
+                    if ((library.getStatus().equals(LibraryStatus.MARKED) || library.getStatus().equals(
+                            LibraryStatus.PROBLEM))
+                            && markService.backLibrary(library, wu.getId())) {
                         obj.accumulate("success", true);
                     } else {
                         obj.accumulate("success", false);
@@ -134,7 +157,8 @@ public class LibraryController extends BaseExamController {
                     obj.accumulate("message", "打回评卷任务失败");
                     log.error("back library error", e);
                 } finally {
-                    lockService.unwatch(LockType.GROUP, library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
+                    lockService.unwatch(LockType.GROUP, library.getExamId(), library.getSubjectCode(),
+                            library.getGroupNumber());
                 }
             } else {
                 obj.accumulate("success", false);
@@ -147,4 +171,27 @@ public class LibraryController extends BaseExamController {
         return obj;
     }
 
+    @RequestMapping(value = "/getJson", method = RequestMethod.GET)
+    @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER, Role.SCHOOL_VIEWER })
+    public String getJson(Model model, HttpServletRequest request, @RequestParam Integer studentId,
+            @RequestParam(required = false) Integer groupNumber) {
+        int examId = getSessionExamId(request);
+        ExamStudent student = studentService.findById(studentId);
+        if (groupNumber != null) {
+            List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                    student.getSubjectCode(), false, groupNumber);
+            List<String> strings = new ArrayList<String>();
+            for (ExamQuestion examQuestion : questions) {
+                strings.add(examQuestion.getQuestionNumber());
+            }
+            String questionNumbers = String.join(",", strings);
+            model.addAttribute("questionNumbers", questionNumbers);
+        }
+        model.addAttribute("jsonServer", jsonServer);
+        model.addAttribute(
+                "answerUrl",
+                PictureUrlBuilder.getAnswerJson(examId, student.getSubjectCode(), student.getPaperType(),
+                        student.getExamNumber()));
+        return "modules/exam/jsonView";
+    }
 }

+ 90 - 60
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkGroupController.java

@@ -30,12 +30,14 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 import cn.com.qmth.stmms.admin.dto.ExamQuestionDTO;
 import cn.com.qmth.stmms.biz.campus.model.Campus;
 import cn.com.qmth.stmms.biz.campus.service.CampusService;
+import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.query.ExamStudentSearchQuery;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
@@ -45,6 +47,7 @@ import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
 import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.common.annotation.RoleRequire;
 import cn.com.qmth.stmms.common.domain.WebUser;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkMode;
 import cn.com.qmth.stmms.common.enums.MarkStatus;
@@ -87,6 +90,9 @@ public class MarkGroupController extends BaseExamController {
     @Autowired
     private LockService lockService;
 
+    @Autowired
+    private ExamService examService;
+
     @Value("${slice.image.server}")
     private String imageServer;
 
@@ -103,33 +109,35 @@ public class MarkGroupController extends BaseExamController {
         }
         List<MarkGroup> list = groupService.findByExamAndSubject(examId, subjectCode);
         for (MarkGroup group : list) {
-            group.setQuestionList(
-                    questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, group.getNumber()));
+            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode,
+                    false, group.getNumber()));
             group.setMarkerCount(markerService.countByExamAndSubjectAndGroup(examId, subjectCode, group.getNumber()));
             group.setCurrentCount(markService.applyCount(group));
-            int percent = group.getLibraryCount() > 0 ? (int) (group.getMarkedCount() * 100.00 / group.getLibraryCount()) : 0;
+            int percent = group.getLibraryCount() > 0 ? (int) (group.getMarkedCount() * 100.00 / group
+                    .getLibraryCount()) : 0;
             group.setPercent(percent);
         }
         model.addAttribute("resultList", list);
         model.addAttribute("subject", subject);
         model.addAttribute("subjectList", getExamSubject(examId, wu));
+        Exam exam = examService.findById(examId);
+        model.addAttribute("examType", exam.getType());
         return "modules/exam/groupList";
     }
 
     @RequestMapping("/query")
     @ResponseBody
-    public JSONArray query(HttpServletRequest request, @RequestParam String subjectCode, @RequestParam(required = false) Boolean withDouble,
-            @RequestParam(required = false) MarkStatus status) {
+    public JSONArray query(HttpServletRequest request, @RequestParam String subjectCode,
+            @RequestParam(required = false) Boolean withDouble, @RequestParam(required = false) MarkStatus status) {
         int examId = getSessionExamId(request);
         JSONArray array = new JSONArray();
-        List<MarkGroup> list = withDouble != null && withDouble ?
-                groupService.findByExamAndSubjectWithDouble(examId, subjectCode) :
-                (status != null ?
-                        groupService.findByExamAndSubjectAndStatus(examId, subjectCode, status) :
-                        groupService.findByExamAndSubject(examId, subjectCode));
+        List<MarkGroup> list = withDouble != null && withDouble ? groupService.findByExamAndSubjectWithDouble(examId,
+                subjectCode) : (status != null ? groupService
+                .findByExamAndSubjectAndStatus(examId, subjectCode, status) : groupService.findByExamAndSubject(examId,
+                subjectCode));
         for (MarkGroup group : list) {
-            group.setQuestionList(
-                    questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, group.getNumber()));
+            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode,
+                    false, group.getNumber()));
             JSONObject obj = new JSONObject();
             obj.accumulate("number", group.getNumber());
             obj.accumulate("title", group.getTitle());
@@ -156,8 +164,8 @@ public class MarkGroupController extends BaseExamController {
 
     @RequestMapping("/release")
     @RoleRequire(Role.SCHOOL_ADMIN)
-    public String release(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number) {
+    public String release(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         if (group == null) {
@@ -175,8 +183,8 @@ public class MarkGroupController extends BaseExamController {
 
     @RequestMapping("/reset")
     @RoleRequire(Role.SCHOOL_ADMIN)
-    public String reset(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number) {
+    public String reset(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         if (group == null) {
@@ -210,13 +218,15 @@ public class MarkGroupController extends BaseExamController {
             boolean allow = false;
             if (group.getStatus() == MarkStatus.TRIAL && status == MarkStatus.FORMAL) {
                 allow = true;
-            } else if (group.getStatus() == MarkStatus.FORMAL && status == MarkStatus.FINISH && group.getLeftCount() == 0) {
+            } else if (group.getStatus() == MarkStatus.FORMAL && status == MarkStatus.FINISH
+                    && group.getLeftCount() == 0) {
                 allow = true;
             } else if (group.getStatus() == MarkStatus.FINISH && status == MarkStatus.FORMAL) {
                 allow = true;
             }
             if (allow) {
-                if (groupService.updateStatus(examId, subjectCode, number, status, group.getStatus()) && status == MarkStatus.FORMAL) {
+                if (groupService.updateStatus(examId, subjectCode, number, status, group.getStatus())
+                        && status == MarkStatus.FORMAL) {
                     // 切换到正评成功后刷新任务数量
                     group.setStatus(status);
                     markService.updateLibraryCount(group);
@@ -246,6 +256,8 @@ public class MarkGroupController extends BaseExamController {
         model.addAttribute("markModeList", MarkMode.values());
         model.addAttribute("scorePolicyList", ScorePolicy.values());
         model.addAttribute("thirdPolicyList", ThirdPolicy.values());
+        Exam exam = examService.findById(examId);
+        model.addAttribute("examType", exam.getType());
         return "modules/exam/groupAdd";
     }
 
@@ -258,15 +270,19 @@ public class MarkGroupController extends BaseExamController {
         if (group != null) {
             String pictureConfig = buildPictureConfig(group);
             group.setPicList(pictureConfig);
-            group.setQuestionList(
-                    questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, group.getNumber()));
+            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode,
+                    false, group.getNumber()));
             model.addAttribute("group", group);
-            model.addAttribute("questions", questionService
-                    .findByExamAndSubjectAndObjectiveAndGroupNumber(group.getExamId(), group.getSubjectCode(), false, group.getNumber()));
+            model.addAttribute(
+                    "questions",
+                    questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(group.getExamId(),
+                            group.getSubjectCode(), false, group.getNumber()));
             model.addAttribute("pictureConfig", pictureConfig);
             model.addAttribute("markModeList", MarkMode.values());
             model.addAttribute("scorePolicyList", ScorePolicy.values());
             model.addAttribute("thirdPolicyList", ThirdPolicy.values());
+            Exam exam = examService.findById(examId);
+            model.addAttribute("examType", exam.getType());
             return "modules/exam/groupEditSimple";
         } else {
             redirectAttributes.addAttribute("subjectCode", subjectCode);
@@ -276,19 +292,20 @@ public class MarkGroupController extends BaseExamController {
 
     @RequestMapping("/edit-full")
     @RoleRequire(Role.SCHOOL_ADMIN)
-    public String editFull(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number) {
+    public String editFull(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         if (group != null) {
             String pictureConfig = buildPictureConfig(group);
             group.setPicList(pictureConfig);
-            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, number));
+            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode,
+                    false, number));
             List<MarkGroup> questionList = new ArrayList<MarkGroup>();
             String[] mainNumbers = group.getMainNumber().split(",");
             for (String mainNumber : mainNumbers) {
-                List<ExamQuestion> list = questionService
-                        .findByExamAndSubjectAndObjectiveAndMainNumber(examId, subjectCode, false, Integer.parseInt(mainNumber));
+                List<ExamQuestion> list = questionService.findByExamAndSubjectAndObjectiveAndMainNumber(examId,
+                        subjectCode, false, Integer.parseInt(mainNumber));
                 StringBuilder score = new StringBuilder();
                 DecimalFormat format = new DecimalFormat("###.#");
                 String title = "";
@@ -311,6 +328,8 @@ public class MarkGroupController extends BaseExamController {
             model.addAttribute("markModeList", MarkMode.values());
             model.addAttribute("scorePolicyList", ScorePolicy.values());
             model.addAttribute("thirdPolicyList", ThirdPolicy.values());
+            Exam exam = examService.findById(examId);
+            model.addAttribute("examType", exam.getType());
             return "modules/exam/groupEditFull";
         } else {
             redirectAttributes.addAttribute("subjectCode", subjectCode);
@@ -320,8 +339,8 @@ public class MarkGroupController extends BaseExamController {
 
     @RequestMapping("/delete")
     @RoleRequire(Role.SCHOOL_ADMIN)
-    public String delete(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number) {
+    public String delete(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         if (group == null) {
@@ -347,9 +366,10 @@ public class MarkGroupController extends BaseExamController {
     @RequestMapping("/save")
     @RoleRequire(Role.SCHOOL_ADMIN)
     @Transactional
-    public String save(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number, @RequestParam Boolean reset, @RequestParam(required = false) String picList,
-            @RequestParam(required = false) Double doubleRate, @RequestParam(required = false) Double arbitrateThreshold,
+    public String save(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number, @RequestParam Boolean reset,
+            @RequestParam(required = false) String picList, @RequestParam(required = false) Double doubleRate,
+            @RequestParam(required = false) Double arbitrateThreshold,
             @RequestParam(required = false) Integer thirdPolicy, @RequestParam(required = false) Integer scorePolicy,
             @RequestParam(required = false) MarkMode markMode, @RequestParam(required = false) Integer trialCount,
             @RequestParam(required = false) boolean sheetView, @RequestParam(required = false) String questionDetail,
@@ -363,9 +383,10 @@ public class MarkGroupController extends BaseExamController {
                     questionDetail = StringEscapeUtils.unescapeHtml(questionDetail);
                     JSONArray array = JSONArray.fromObject(questionDetail);
                     List<ExamQuestionDTO> detailList = JSONArray.toList(array, new ExamQuestionDTO(), new JsonConfig());
-                    List<ExamQuestion> all = questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false);
-                    List<ExamQuestion> old = questionService
-                            .findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, number);
+                    List<ExamQuestion> all = questionService.findByExamAndSubjectAndObjective(examId, subjectCode,
+                            false);
+                    List<ExamQuestion> old = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                            subjectCode, false, number);
                     Set<Integer> mainNumbers = new HashSet<Integer>();
                     for (ExamQuestion examQuestion : all) {
                         mainNumbers.add(examQuestion.getMainNumber());
@@ -390,22 +411,25 @@ public class MarkGroupController extends BaseExamController {
                     }
                     if (detailList != null && detailList.size() > 0) {
                         ScorePolicy policy = scorePolicy != null ? ScorePolicy.findByValue(scorePolicy) : null;
-                        ThirdPolicy third = thirdPolicy != null ? ThirdPolicy.findByValue(thirdPolicy) : ThirdPolicy.DISABLE;
+                        ThirdPolicy third = thirdPolicy != null ? ThirdPolicy.findByValue(thirdPolicy)
+                                : ThirdPolicy.DISABLE;
                         try {
-                            lockService.waitlock(LockType.GROUP, true, group.getExamId(), group.getSubjectCode(), group.getNumber());
+                            lockService.waitlock(LockType.GROUP, true, group.getExamId(), group.getSubjectCode(),
+                                    group.getNumber());
                             markService.updateGroup(group, buildQuestionList(group, detailList), policy, third);
                         } catch (Exception e) {
                             log.error("update group error", e);
                             throw new RuntimeException("重置更新大题失败", e);
                         } finally {
-                            lockService.unlock(LockType.GROUP, true, group.getExamId(), group.getSubjectCode(), group.getNumber());
+                            lockService.unlock(LockType.GROUP, true, group.getExamId(), group.getSubjectCode(),
+                                    group.getNumber());
                         }
                     }
                 } else {
                     // simple update
                     List<Double> intervalScores = buildDoubleList(intervalScoreList);
-                    List<ExamQuestion> questionList = questionService
-                            .findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, number);
+                    List<ExamQuestion> questionList = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(
+                            examId, subjectCode, false, number);
                     if (intervalScores.size() == questionList.size()) {
                         for (int i = 0; i < questionList.size(); i++) {
                             ExamQuestion q = questionList.get(i);
@@ -455,13 +479,15 @@ public class MarkGroupController extends BaseExamController {
     @RequestMapping("/insert")
     @RoleRequire(Role.SCHOOL_ADMIN)
     @Transactional
-    public String insert(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number, @RequestParam String questionDetail, @RequestParam String picList,
-            @RequestParam(required = false) Double doubleRate, @RequestParam(required = false) Double arbitrateThreshold,
+    public String insert(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number, @RequestParam String questionDetail,
+            @RequestParam String picList, @RequestParam(required = false) Double doubleRate,
+            @RequestParam(required = false) Double arbitrateThreshold,
             @RequestParam(required = false) Integer thirdPolicy, @RequestParam(required = false) Integer scorePolicy,
             @RequestParam(required = false) String markMode, @RequestParam(required = false) Integer trialCount,
             @RequestParam(required = false) boolean sheetView) {
         int examId = getSessionExamId(request);
+        Exam exam = examService.findById(examId);
         MarkGroup group = groupService.findOne(examId, subjectCode, number);
         if (group != null) {
             addMessage(redirectAttributes, "评卷分组序号不能重复");
@@ -471,7 +497,7 @@ public class MarkGroupController extends BaseExamController {
             addMessage(redirectAttributes, "大题详情必须设置");
             redirectAttributes.addAttribute("subjectCode", subjectCode);
             return "redirect:/admin/exam/group/add";
-        } else if (StringUtils.isBlank(picList)) {
+        } else if (StringUtils.isBlank(picList) && !exam.getType().equals(ExamType.MULTI_MEDIA)) {
             addMessage(redirectAttributes, "图片范围必须设置");
             redirectAttributes.addAttribute("subjectCode", subjectCode);
             return "redirect:/admin/exam/group/add";
@@ -479,12 +505,15 @@ public class MarkGroupController extends BaseExamController {
             try {
                 // create group
                 // build picList
-                picList = StringEscapeUtils.unescapeHtml(picList);
-                JSONArray array = JSONArray.fromObject(picList);
-                List<PictureConfigItem> picConfigList = JSONArray.toList(array, new PictureConfigItem(), new JsonConfig());
+                List<PictureConfigItem> picConfigList = null;
+                if (!exam.getType().equals(ExamType.MULTI_MEDIA)) {
+                    picList = StringEscapeUtils.unescapeHtml(picList);
+                    JSONArray array = JSONArray.fromObject(picList);
+                    picConfigList = JSONArray.toList(array, new PictureConfigItem(), new JsonConfig());
+                }
                 // build questionDetail
                 questionDetail = StringEscapeUtils.unescapeHtml(questionDetail);
-                array = JSONArray.fromObject(questionDetail);
+                JSONArray array = JSONArray.fromObject(questionDetail);
                 List<ExamQuestionDTO> detailList = JSONArray.toList(array, new ExamQuestionDTO(), new JsonConfig());
                 for (int i = 0; i < detailList.size(); i++) {
                     ExamQuestionDTO dto = detailList.get(i);
@@ -495,23 +524,25 @@ public class MarkGroupController extends BaseExamController {
                     }
                     dto.setScoreList(scoreList);
                 }
-                if (picConfigList != null && detailList != null && picConfigList.size() > 0 && detailList.size() > 0) {
+                if (detailList != null && detailList.size() > 0) {
                     for (ExamQuestionDTO detail : detailList) {
-                        if (questionService
-                                .countByExamAndSubjectAndObjectiveAndMainNumber(examId, subjectCode, false, detail.getMainNumber()) > 0) {
+                        if (questionService.countByExamAndSubjectAndObjectiveAndMainNumber(examId, subjectCode, false,
+                                detail.getMainNumber()) > 0) {
                             addMessage(redirectAttributes, "大题号不能重复");
                             redirectAttributes.addAttribute("subjectCode", subjectCode);
                             return "redirect:/admin/exam/group/add";
                         }
                     }
-                    group = new MarkGroup(examId, subjectCode, number, picConfigList, 0d, doubleRate, arbitrateThreshold, scorePolicy,
-                            markMode, trialCount, sheetView, thirdPolicy);
+                    group = new MarkGroup(examId, subjectCode, number, picConfigList, 0d, doubleRate,
+                            arbitrateThreshold, scorePolicy, markMode, trialCount, sheetView, thirdPolicy);
                     // clear and replace exam_question
-                    questionService.deleteByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, number);
+                    questionService
+                            .deleteByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, number);
                     List<ExamQuestion> list = buildQuestionList(group, detailList);
                     questionService.save(list);
                     groupService.save(group);
-                    subjectService.updateScore(examId, subjectCode, false, groupService.sumTotalScore(examId, subjectCode));
+                    subjectService.updateScore(examId, subjectCode, false,
+                            groupService.sumTotalScore(examId, subjectCode));
                     redirectAttributes.addAttribute("subjectCode", subjectCode);
                     return "redirect:/admin/exam/group";
                 } else {
@@ -530,8 +561,8 @@ public class MarkGroupController extends BaseExamController {
 
     @RequestMapping("/getPictureConfig")
     @RoleRequire(Role.SCHOOL_ADMIN)
-    public String get(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer number) {
+    public String get(HttpServletRequest request, Model model, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer number) {
         int examId = getSessionExamId(request);
         ExamStudentSearchQuery query = new ExamStudentSearchQuery();
         query.setExamId(examId);
@@ -569,9 +600,8 @@ public class MarkGroupController extends BaseExamController {
 
     private List<String> buildPicUrl(ExamStudent examStudent) {
         Campus campus = campusService.findBySchoolAndName(examStudent.getSchoolId(), examStudent.getCampusName());
-        List<String> picUrls = PictureUrlBuilder
-                .getSliceUrls(examStudent.getExamId(), campus.getId(), examStudent.getSubjectCode(), examStudent.getExamNumber(),
-                        examStudent.getSliceCount());
+        List<String> picUrls = PictureUrlBuilder.getSliceUrls(examStudent.getExamId(), campus.getId(),
+                examStudent.getSubjectCode(), examStudent.getExamNumber(), examStudent.getSliceCount());
         return picUrls;
     }
 

+ 41 - 21
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/MarkQualityController.java

@@ -22,11 +22,13 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 
 import cn.com.qmth.stmms.admin.vo.MarkerVO;
+import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.model.Marker;
 import cn.com.qmth.stmms.biz.exam.query.MarkerSearchQuery;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
@@ -42,6 +44,7 @@ import cn.com.qmth.stmms.biz.mark.service.TrialService;
 import cn.com.qmth.stmms.biz.mark.thread.MarkQualityThread;
 import cn.com.qmth.stmms.common.annotation.RoleRequire;
 import cn.com.qmth.stmms.common.domain.WebUser;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.enums.LibraryStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkStatus;
@@ -85,6 +88,9 @@ public class MarkQualityController extends BaseExamController {
     @Autowired
     private ExamStudentService studentService;
 
+    @Autowired
+    private ExamService examService;
+
     @Value("${slice.image.server}")
     private String sliceServer;
 
@@ -94,6 +100,9 @@ public class MarkQualityController extends BaseExamController {
     @Value("${card.server}")
     private String cardServer;
 
+    @Value("${json.server}")
+    private String jsonServer;
+
     @RequestMapping
     public String list(Model model, HttpServletRequest request, MarkerSearchQuery query) {
         int examId = getSessionExamId(request);
@@ -114,8 +123,10 @@ public class MarkQualityController extends BaseExamController {
         }
         if (query.getSubjectCode() != null && query.getGroupNumber() != null) {
             query = markerService.findByQuery(query);
-            model.addAttribute("running",
-                    lockService.isLocked(LockType.BATCH_QUALITY, getLockKey(examId, query.getSubjectCode(), query.getGroupNumber())));
+            model.addAttribute(
+                    "running",
+                    lockService.isLocked(LockType.BATCH_QUALITY,
+                            getLockKey(examId, query.getSubjectCode(), query.getGroupNumber())));
         }
         model.addAttribute("query", query);
         model.addAttribute("subjectList", subjectList);
@@ -124,15 +135,15 @@ public class MarkQualityController extends BaseExamController {
     }
 
     @RequestMapping("/update")
-    public String update(HttpServletRequest request, RedirectAttributes redirectAttributes, @RequestParam String subjectCode,
-            @RequestParam Integer groupNumber) {
+    public String update(HttpServletRequest request, RedirectAttributes redirectAttributes,
+            @RequestParam String subjectCode, @RequestParam Integer groupNumber) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
         if (group != null) {
             final String lockKey = getLockKey(examId, subjectCode, groupNumber);
             if (lockService.trylock(LockType.BATCH_QUALITY, lockKey)) {
-                taskExecutor.submit(new MarkQualityThread(markerService.findByExamAndSubjectAndGroup(examId, subjectCode, groupNumber),
-                        lockKey));
+                taskExecutor.submit(new MarkQualityThread(markerService.findByExamAndSubjectAndGroup(examId,
+                        subjectCode, groupNumber), lockKey));
             }
             redirectAttributes.addAttribute("groupNumber", groupNumber);
         }
@@ -145,16 +156,18 @@ public class MarkQualityController extends BaseExamController {
     }
 
     @RequestMapping("/chart")
-    public String chart(HttpServletRequest request, Model model, @RequestParam String subjectCode, @RequestParam Integer groupNumber) {
+    public String chart(HttpServletRequest request, Model model, @RequestParam String subjectCode,
+            @RequestParam Integer groupNumber) {
         int examId = getSessionExamId(request);
         MarkGroup group = groupService.findOne(examId, subjectCode, groupNumber);
         if (group != null) {
             List<MarkerVO> list = new ArrayList<MarkerVO>();
             List<Marker> markers = markerService.findByExamAndSubjectAndGroup(examId, subjectCode, groupNumber);
-            List<Double> scores = libraryService.findScore(examId, subjectCode, groupNumber, LibraryStatus.MARKED, LibraryStatus.INSPECTED);
+            List<Double> scores = libraryService.findScore(examId, subjectCode, groupNumber, LibraryStatus.MARKED,
+                    LibraryStatus.INSPECTED);
             for (Marker marker : markers) {
-                List<Object[]> libraries = libraryService
-                        .findScoreCount(examId, subjectCode, groupNumber, marker.getId(), LibraryStatus.MARKED, LibraryStatus.INSPECTED);
+                List<Object[]> libraries = libraryService.findScoreCount(examId, subjectCode, groupNumber,
+                        marker.getId(), LibraryStatus.MARKED, LibraryStatus.INSPECTED);
                 Map<Double, Long> scoreCount = new HashMap<Double, Long>();
                 for (Object[] array : libraries) {
                     Double score = (Double) array[0];
@@ -168,8 +181,8 @@ public class MarkQualityController extends BaseExamController {
                 vo.setScoreCount(scoreCount);
                 list.add(vo);
             }
-            group.setQuestionList(
-                    questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode, false, group.getNumber()));
+            group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId, subjectCode,
+                    false, group.getNumber()));
             model.addAttribute("scores", scores);
             model.addAttribute("markers", list);
             model.addAttribute("group", group);
@@ -182,13 +195,14 @@ public class MarkQualityController extends BaseExamController {
 
     @RequestMapping("/getChart")
     @ResponseBody
-    public List<MarkerVO> getChart(HttpServletRequest request, @RequestParam String subjectCode, @RequestParam Integer groupNumber) {
+    public List<MarkerVO> getChart(HttpServletRequest request, @RequestParam String subjectCode,
+            @RequestParam Integer groupNumber) {
         int examId = getSessionExamId(request);
         List<MarkerVO> list = new ArrayList<MarkerVO>();
         List<Marker> markers = markerService.findByExamAndSubjectAndGroup(examId, subjectCode, groupNumber);
         for (Marker marker : markers) {
-            List<Object[]> libraries = libraryService
-                    .findScoreCount(examId, subjectCode, groupNumber, marker.getId(), LibraryStatus.MARKED, LibraryStatus.INSPECTED);
+            List<Object[]> libraries = libraryService.findScoreCount(examId, subjectCode, groupNumber, marker.getId(),
+                    LibraryStatus.MARKED, LibraryStatus.INSPECTED);
             MarkLibrarySearchQuery query = new MarkLibrarySearchQuery();
             query.setMarkerId(marker.getId());
             query.addStatus(LibraryStatus.MARKED);
@@ -216,7 +230,8 @@ public class MarkQualityController extends BaseExamController {
 
     @RequestMapping("/batchProcess")
     @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
-    public String batchProcess(Model model, HttpServletRequest request, @RequestParam Integer markerId, @RequestParam Double markerScore) {
+    public String batchProcess(Model model, HttpServletRequest request, @RequestParam Integer markerId,
+            @RequestParam Double markerScore) {
         int examId = getSessionExamId(request);
         Marker marker = markerService.findById(markerId);
         WebUser wu = RequestUtils.getWebUser(request);
@@ -230,18 +245,24 @@ public class MarkQualityController extends BaseExamController {
         model.addAttribute("sheetServer", sheetServer);
         model.addAttribute("sliceServer", sliceServer);
         model.addAttribute("cardServer", cardServer);
+        model.addAttribute("jsonServer", jsonServer);
         model.addAttribute("subject", subjectService.find(group.getExamId(), group.getSubjectCode()));
         model.addAttribute("group", group);
         model.addAttribute("markerId", markerId);
         model.addAttribute("markerScore", markerScore);
+        Exam exam = examService.findById(examId);
+        if (ExamType.MULTI_MEDIA.equals(exam.getType())) {
+            return "modules/exam/qualityProcessJson";
+        }
         return "modules/exam/qualityProcess";
     }
 
     @RequestMapping(value = "/history", method = RequestMethod.POST)
     @ResponseBody
     @RoleRequire({ Role.SCHOOL_ADMIN, Role.SUBJECT_HEADER })
-    public List<Task> getTask(HttpServletRequest request, @RequestParam Integer markerId, @RequestParam Double markerScore,
-            @RequestParam(required = false) Integer pageNumber, @RequestParam(required = false) Integer pageSize) {
+    public List<Task> getTask(HttpServletRequest request, @RequestParam Integer markerId,
+            @RequestParam Double markerScore, @RequestParam(required = false) Integer pageNumber,
+            @RequestParam(required = false) Integer pageSize) {
         Marker marker = markerService.findById(markerId);
         List<Task> list = new ArrayList<>();
         MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
@@ -272,9 +293,8 @@ public class MarkQualityController extends BaseExamController {
         } else if (group != null && group.getStatus() == MarkStatus.TRIAL) {
             // 试评查找给分历史记录
             List<TrialHistory> historyList = new ArrayList<TrialHistory>();
-            historyList = trialService
-                    .findHistory(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber(), marker.getId(), pageNumber, pageSize,
-                            null, markerScore);
+            historyList = trialService.findHistory(marker.getExamId(), marker.getSubjectCode(),
+                    marker.getGroupNumber(), marker.getId(), pageNumber, pageSize, null, markerScore);
             for (TrialHistory history : historyList) {
                 TrialLibrary library = trialService.findLibrary(history.getLibraryId());
                 if (library != null) {

+ 19 - 17
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/PaperController.java

@@ -31,6 +31,7 @@ import cn.com.qmth.stmms.admin.dto.ObjectiveQuestionDTO;
 import cn.com.qmth.stmms.admin.dto.QuestionDTO;
 import cn.com.qmth.stmms.admin.dto.SubjectQuestionDTO;
 import cn.com.qmth.stmms.admin.dto.SubjectiveQuestionDTO;
+import cn.com.qmth.stmms.biz.exam.model.Exam;
 import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
@@ -88,6 +89,8 @@ public class PaperController extends BaseExamController {
         model.addAttribute("levelList", subjectService.listLevel(examId));
         model.addAttribute("categoryList", subjectService.listCategory(examId));
         model.addAttribute("cardServer", cardServer);
+        Exam exam = examService.findById(examId);
+        model.addAttribute("examType", exam.getType());
         return "modules/exam/paperList";
     }
 
@@ -118,14 +121,15 @@ public class PaperController extends BaseExamController {
     }
 
     @RequestMapping(value = "/template")
-    public String importTemplate(HttpServletResponse response, @RequestParam Boolean objective, RedirectAttributes redirectAttributes) {
+    public String importTemplate(HttpServletResponse response, @RequestParam Boolean objective,
+            RedirectAttributes redirectAttributes) {
         try {
             String fileName = objective ? "客观题导入模板.xlsx" : "主观题导入模板.xlsx";
             String title = objective ? "客观题数据" : "主观题数据";
             List<QuestionDTO> list = new LinkedList<QuestionDTO>();
             list.add(objective ? new ObjectiveQuestionDTO() : new SubjectiveQuestionDTO());
-            new ExportExcel(title, objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class, 2).setDataList(list)
-                    .write(response, fileName).dispose();
+            new ExportExcel(title, objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class, 2)
+                    .setDataList(list).write(response, fileName).dispose();
             return null;
         } catch (Exception e) {
             addMessage(redirectAttributes, "导入模板下载失败!失败信息:" + e.getMessage());
@@ -159,16 +163,15 @@ public class PaperController extends BaseExamController {
         query.setPageSize(Integer.MAX_VALUE);
         query = questionService.findByQuery(query);
         for (ExamQuestion q : query.getResult()) {
-            list.add(objective ?
-                    new ObjectiveQuestionDTO(q, subjectMap.get(q.getSubjectCode())) :
-                    new SubjectiveQuestionDTO(q, subjectMap.get(q.getSubjectCode()),
-                            groupMap.get(q.getSubjectCode() + "_" + q.getGroupNumber())));
+            list.add(objective ? new ObjectiveQuestionDTO(q, subjectMap.get(q.getSubjectCode()))
+                    : new SubjectiveQuestionDTO(q, subjectMap.get(q.getSubjectCode()), groupMap.get(q.getSubjectCode()
+                            + "_" + q.getGroupNumber())));
         }
         try {
             String fileName = objective ? "客观题数据.xlsx" : "主观题数据.xlsx";
             String title = objective ? "客观题数据" : "主观题数据";
-            new ExportExcel(title, objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class, 2).setDataList(list)
-                    .write(response, fileName).dispose();
+            new ExportExcel(title, objective ? ObjectiveQuestionDTO.class : SubjectiveQuestionDTO.class, 2)
+                    .setDataList(list).write(response, fileName).dispose();
             return null;
         } catch (Exception e) {
             addMessage(redirectAttributes, "导出数据失败!失败信息:" + e.getMessage());
@@ -205,16 +208,14 @@ public class PaperController extends BaseExamController {
                                 if (old == null && group.getQuestionMap() != null) {
                                     for (Entry<Integer, List<ExamQuestion>> entry : group.getQuestionMap().entrySet()) {
                                         // 已存在的大题跳过
-                                        if (questionService
-                                                .countByExamAndSubjectAndObjectiveAndMainNumber(examId, group.getSubjectCode(), objective,
-                                                        entry.getKey()) == 0) {
+                                        if (questionService.countByExamAndSubjectAndObjectiveAndMainNumber(examId,
+                                                group.getSubjectCode(), objective, entry.getKey()) == 0) {
                                             questionService.save(entry.getValue());
                                         }
                                     }
-                                    //有题目的分组才保存
-                                    if (questionService
-                                            .countByExamAndSubjectAndObjectiveAndGroupNumber(examId, group.getSubjectCode(), objective,
-                                                    group.getNumber()) != 0) {
+                                    // 有题目的分组才保存
+                                    if (questionService.countByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                                            group.getSubjectCode(), objective, group.getNumber()) != 0) {
                                         groupService.save(group);
                                     }
                                 }
@@ -240,7 +241,8 @@ public class PaperController extends BaseExamController {
         return "redirect:/admin/exam/paper";
     }
 
-    private Map<String, SubjectQuestionDTO> parseQuestion(MultipartFile file, int examId, boolean objective, List<String> error) {
+    private Map<String, SubjectQuestionDTO> parseQuestion(MultipartFile file, int examId, boolean objective,
+            List<String> error) {
         Map<String, SubjectQuestionDTO> map = new HashMap<String, SubjectQuestionDTO>();
         try {
             ImportExcel excel = new ImportExcel(file, 1, 0);

+ 118 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ProblemHistoryController.java

@@ -0,0 +1,118 @@
+package cn.com.qmth.stmms.admin.exam;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import cn.com.qmth.stmms.biz.exam.model.Exam;
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
+import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
+import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
+import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
+import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
+import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
+import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
+import cn.com.qmth.stmms.biz.mark.model.ProblemHistory;
+import cn.com.qmth.stmms.biz.mark.model.ProblemType;
+import cn.com.qmth.stmms.biz.mark.query.ProblemHistorySearchQuery;
+import cn.com.qmth.stmms.biz.mark.service.ProblemHistoryService;
+import cn.com.qmth.stmms.biz.mark.service.ProblemTypeService;
+import cn.com.qmth.stmms.common.enums.HistoryStatus;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
+import cn.com.qmth.stmms.common.utils.DateUtils;
+
+@Controller
+@RequestMapping("/admin/exam/problem/history")
+public class ProblemHistoryController extends BaseExamController {
+
+    protected static Logger log = LoggerFactory.getLogger(ProblemHistoryController.class);
+
+    @Autowired
+    private ProblemTypeService problemService;
+
+    @Autowired
+    private ProblemHistoryService historyService;
+
+    @Autowired
+    private ExamStudentService studentService;
+
+    @Autowired
+    private ExamSubjectService subjectService;
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private MarkGroupService groupService;
+
+    @Autowired
+    private ExamQuestionService questionService;
+
+    @RequestMapping
+    public String list(HttpServletRequest request, Model model, ProblemHistorySearchQuery query) {
+        int examId = getSessionExamId(request);
+        if (examId > 0) {
+            List<ProblemType> problemTypes = problemService.findByExamId(examId);
+            Map<Integer, ProblemType> problemMap = new HashMap<Integer, ProblemType>();
+            for (ProblemType problemType : problemTypes) {
+                problemMap.put(problemType.getId(), problemType);
+            }
+            query.setExamId(examId);
+            query.setStatus(HistoryStatus.WAITING);
+            query.orderByExamNumber();
+            query = historyService.findByQuery(query);
+
+            List<ExamStudent> list = new LinkedList<ExamStudent>();
+            for (ProblemHistory history : query.getResult()) {
+                ExamStudent student = studentService.findById(history.getStudentId());
+                student.setNumber(history.getGroupNumber());
+                student.setProblemType(problemMap.get(history.getProblemId()));
+                student.setMarkTime(DateUtils.formatDateTime(history.getCreateTime()));
+                student.setLibraryId(history.getLibraryId());
+                list.add(student);
+            }
+            model.addAttribute("resultList", list);
+            model.addAttribute("query", query);
+            model.addAttribute("subjectList", getProblemSubject(examId));
+            model.addAttribute("problemList", problemTypes);
+            List<MarkGroup> groupList = groupService.findByExamAndSubjectAndStatus(examId, query.getSubjectCode(),
+                    MarkStatus.FORMAL);
+            for (MarkGroup group : groupList) {
+                List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                        group.getSubjectCode(), false, group.getNumber());
+                group.setQuestionList(questions);
+            }
+            model.addAttribute("groupList", groupList);
+            model.addAttribute("statusList", HistoryStatus.getOptionList());
+            Exam exam = examService.findById(examId);
+            model.addAttribute("examType", exam.getType());
+            return "modules/exam/problemHistory";
+        }
+        return "redirect:/admin/exam/list";
+    }
+
+    private List<ExamSubject> getProblemSubject(int examId) {
+        List<ExamSubject> list = new LinkedList<ExamSubject>();
+        List<String> codes = historyService.findProblemSubjectCode(examId);
+        for (String code : codes) {
+            ExamSubject subject = subjectService.find(examId, code);
+            if (subject != null) {
+                list.add(subject);
+            }
+        }
+        return list;
+    }
+
+}

+ 2 - 3
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ProblemTypeController.java

@@ -19,11 +19,11 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 
+import cn.com.qmth.stmms.admin.utils.SessionExamUtils;
 import cn.com.qmth.stmms.biz.mark.model.ProblemType;
 import cn.com.qmth.stmms.biz.mark.service.ProblemHistoryService;
 import cn.com.qmth.stmms.biz.mark.service.ProblemTypeService;
 import cn.com.qmth.stmms.common.domain.WebUser;
-import cn.com.qmth.stmms.common.session.model.StmmsSession;
 import cn.com.qmth.stmms.common.utils.RequestUtils;
 
 @Controller
@@ -41,8 +41,7 @@ public class ProblemTypeController extends BaseExamController {
     @RequestMapping
     public String list(HttpServletRequest request, Model model, @RequestParam(required = false) Integer examId) {
         if (examId != null) {
-            StmmsSession session = RequestUtils.getSession(request);
-            session.setParameter("examId", examId.toString());
+            SessionExamUtils.setExamId(request, examId);
         }
         examId = getSessionExamId(request);
         List<ProblemType> list = problemService.findByExamId(examId);

+ 2 - 2
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ScoreCheckController.java

@@ -98,8 +98,8 @@ public class ScoreCheckController extends BaseExamController {
         query = questionService.findByQuery(query);
         for (ExamQuestion question : query.getResult()) {
             if (question.getTotalScore() > 0) {
-                ExceptionQuestionDTO dto = new ExceptionQuestionDTO(question,
-                        subjectService.find(question.getExamId(), question.getSubjectCode()));
+                ExceptionQuestionDTO dto = new ExceptionQuestionDTO(question, subjectService.find(question.getExamId(),
+                        question.getSubjectCode()));
                 list.add(dto);
             }
         }

+ 8 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/ScoreController.java

@@ -100,6 +100,9 @@ public class ScoreController extends BaseExamController {
     @Value("${card.server}")
     private String cardServer;
 
+    @Value("${json.server}")
+    private String jsonServer;
+
     @RequestMapping
     public ModelAndView list(HttpServletRequest request, ExamStudentSearchQuery query, @RequestParam(defaultValue = "0") Integer filter) {
         WebUser wu = RequestUtils.getWebUser(request);
@@ -124,6 +127,8 @@ public class ScoreController extends BaseExamController {
             buildSheetUrl(student);
             buildPackageUrl(student);
             buildAnswerUrl(student);
+            student.setAnswerUrl(PictureUrlBuilder
+                    .getAnswerJson(student.getExamId(), student.getSubjectCode(), student.getPaperType(), student.getExamNumber()));
         }
         String exportMessage = StringUtils.isNotBlank(query.getSubjectCode()) ?
                 enableExport(examId, query.getSubjectCode()) :
@@ -141,6 +146,9 @@ public class ScoreController extends BaseExamController {
         view.addObject("imageServer", imageServer);
         view.addObject("packageServer", packageServer);
         view.addObject("cardServer", cardServer);
+        view.addObject("jsonServer", jsonServer);
+        Exam exam = examService.findById(examId);
+        view.addObject("examType", exam.getType());
         return view;
     }
 

+ 86 - 17
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/StudentController.java

@@ -25,6 +25,7 @@ import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 
 import cn.com.qmth.stmms.admin.vo.ExamStudentVO;
+import cn.com.qmth.stmms.admin.vo.UploadStudentVO;
 import cn.com.qmth.stmms.biz.campus.model.Campus;
 import cn.com.qmth.stmms.biz.campus.service.CampusService;
 import cn.com.qmth.stmms.biz.exam.model.Exam;
@@ -102,6 +103,8 @@ public class StudentController extends BaseExamController {
         model.addAttribute("categoryList", subjectService.listCategory(examId));
         model.addAttribute("imageServer", imageServer);
         model.addAttribute("packageServer", packageServer);
+        Exam exam = examService.findById(examId);
+        model.addAttribute("examType", exam.getType());
         return "modules/exam/studentList";
     }
 
@@ -128,8 +131,8 @@ public class StudentController extends BaseExamController {
 
     @RequestMapping(value = "/save")
     @RoleRequire(Role.SCHOOL_ADMIN)
-    public String save(ExamStudent student, Model model, RedirectAttributes redirectAttributes, HttpServletRequest request,
-            HttpServletResponse response) {
+    public String save(ExamStudent student, Model model, RedirectAttributes redirectAttributes,
+            HttpServletRequest request, HttpServletResponse response) {
         int examId = getSessionExamId(request);
         Exam exam = examService.findById(examId);
         ExamSubject subject = subjectService.find(examId, student.getSubjectCode());
@@ -144,7 +147,8 @@ public class StudentController extends BaseExamController {
                 if (previous != null) {
                     // String previousSubjectCode = previous.getSubjectCode();
                     student.setExamId(examId);
-                    ExamStudent old = checkExamNumber(student, new HashMap<String, ExamStudent>(), new HashMap<String, ExamStudent>());
+                    ExamStudent old = checkExamNumber(student, new HashMap<String, ExamStudent>(),
+                            new HashMap<String, ExamStudent>());
                     if (old != null && !old.getExamNumber().equals(previous.getExamNumber())) {
                         addMessage(redirectAttributes, "准考证号" + student.getExamNumber() + "已经存在");
                         return "redirect:/admin/exam/student";
@@ -195,7 +199,8 @@ public class StudentController extends BaseExamController {
                 student.setSheetCount(0);
                 student.setObjectiveScore(0d);
                 student.setSubjectiveScore(0d);
-                ExamStudent old = checkExamNumber(student, new HashMap<String, ExamStudent>(), new HashMap<String, ExamStudent>());
+                ExamStudent old = checkExamNumber(student, new HashMap<String, ExamStudent>(),
+                        new HashMap<String, ExamStudent>());
                 if (old != null) {
                     addMessage(redirectAttributes, "准考证号" + student.getExamNumber() + "已经存在");
                     return "redirect:/admin/exam/student";
@@ -215,8 +220,11 @@ public class StudentController extends BaseExamController {
         if (student != null) {
             studentService.deleteById(id);
             markService.deleteByStudent(student);
-            subjectService.updateUploadCount(student.getExamId(), student.getSubjectCode(),
-                    (int) studentService.countUploadedByExamIdAndSubjectCode(student.getExamId(), student.getSubjectCode()));
+            subjectService.updateUploadCount(
+                    student.getExamId(),
+                    student.getSubjectCode(),
+                    (int) studentService.countUploadedByExamIdAndSubjectCode(student.getExamId(),
+                            student.getSubjectCode()));
             addMessage(redirectAttributes, "删除考生成功");
         } else {
             addMessage(redirectAttributes, "找不到对应的考生");
@@ -262,11 +270,12 @@ public class StudentController extends BaseExamController {
             }
 
             for (ExamStudent student : list) {
-                if (StringUtils.isBlank(student.getExamNumber()) || StringUtils.isBlank(student.getName()) || StringUtils
-                        .isBlank(student.getSubjectCode()) || StringUtils.isBlank(student.getSubjectName()) || StringUtils
-                        .isBlank(student.getCampusName()) || StringUtils.isBlank(student.getCollege()) || StringUtils
-                        .isBlank(student.getClassName()) || StringUtils.isBlank(student.getTeacher()) || StringUtils
-                        .isBlank(student.getStudentCode())) {
+                if (StringUtils.isBlank(student.getExamNumber()) || StringUtils.isBlank(student.getName())
+                        || StringUtils.isBlank(student.getSubjectCode())
+                        || StringUtils.isBlank(student.getSubjectName())
+                        || StringUtils.isBlank(student.getCampusName()) || StringUtils.isBlank(student.getCollege())
+                        || StringUtils.isBlank(student.getClassName()) || StringUtils.isBlank(student.getTeacher())
+                        || StringUtils.isBlank(student.getStudentCode())) {
                     continue;
                 }
 
@@ -320,7 +329,8 @@ public class StudentController extends BaseExamController {
                 student.setSubjectRemark(subject != null ? subject.getRemark() : "");
             }
             String fileName = "考生数据.xlsx";
-            new ExportExcel("考生数据", ExamStudent.class).setDataList(query.getResult()).write(response, fileName).dispose();
+            new ExportExcel("考生数据", ExamStudent.class).setDataList(query.getResult()).write(response, fileName)
+                    .dispose();
             return null;
         } catch (Exception e) {
             addMessage(redirectAttributes, "导出考生数据失败!" + e.getMessage());
@@ -459,7 +469,66 @@ public class StudentController extends BaseExamController {
         return result;
     }
 
-    private ExamStudent checkExamNumber(ExamStudent student, Map<String, ExamStudent> current, Map<String, ExamStudent> saveMap) {
+    @RequestMapping(value = "/uploadTemplate")
+    public String uploadTemplate(HttpServletResponse response, RedirectAttributes redirectAttributes) {
+        try {
+            String fileName = "多媒体考生上传导入模板.xlsx";
+            List<UploadStudentVO> list = Lists.newArrayList();
+            list.add(new UploadStudentVO());
+            new ExportExcel("多媒体考生上传", UploadStudentVO.class, 2).setDataList(list).write(response, fileName).dispose();
+            return null;
+        } catch (Exception e) {
+            addMessage(redirectAttributes, "导入模板下载失败!失败信息:" + e.getMessage());
+        }
+        return "redirect:/admin/exam/student";
+    }
+
+    @RequestMapping(value = "/uploadImport", method = RequestMethod.POST)
+    public String uploadImportFile(HttpServletRequest request, MultipartFile file, RedirectAttributes redirectAttributes) {
+        int examId = getSessionExamId(request);
+        // Exam exam = examService.findById(examId);
+        try {
+            int successNum = 0;
+            int failureNum = 0;
+            StringBuilder failureMsg = new StringBuilder();
+            ImportExcel ei = new ImportExcel(file, 1, 0);
+            List<UploadStudentVO> list = ei.getDataList(UploadStudentVO.class);
+            for (UploadStudentVO studentVO : list) {
+                if (StringUtils.isBlank(studentVO.getExamNumber())) {
+                    continue;
+                }
+                ExamStudent student = studentService.findByExamIdAndExamNumber(examId, studentVO.getExamNumber());
+                if (student != null) {
+                    student.setUpload(true);
+                    student.setAbsent(false);
+                    student.setAnswers(null);
+                    student.setBatchCode(null);
+                    student.setSliceCount(0);
+                    student.setSheetCount(0);
+                    student.setPaperType(StringUtils.trimToNull(studentVO.getPaperType()));
+                    // 同步更新评卷任务
+                    if (saveUploadStudent(student)) {
+                        successNum++;
+                    }
+                } else {
+                    failureMsg.append("<br/>准考证号 " + studentVO.getExamNumber() + " 不存在; ");
+                    failureNum++;
+                }
+
+            }
+            if (failureNum > 0) {
+                failureMsg.insert(0, ",失败 " + failureNum + " 条用户");
+            }
+            addMessage(redirectAttributes, "已成功导入 " + successNum + " 条用户" + failureMsg);
+        } catch (Exception e) {
+            log.error("Batch import BreachStudent error!", e);
+            addMessage(redirectAttributes, "导入上传考生失败!失败信息:" + e.getMessage());
+        }
+        return "redirect:/admin/exam/student";
+    }
+
+    private ExamStudent checkExamNumber(ExamStudent student, Map<String, ExamStudent> current,
+            Map<String, ExamStudent> saveMap) {
         ExamStudent previous = saveMap.get(student.getExamNumber());
 
         if (previous != null) {
@@ -491,16 +560,16 @@ public class StudentController extends BaseExamController {
 
     private void buildSheetUrl(ExamStudent student) {
         Campus campus = campusService.findBySchoolAndName(student.getSchoolId(), student.getCampusName());
-        student.setSheetUrls(PictureUrlBuilder
-                .getSheetUrls(student.getExamId(), campus.getId(), student.getSubjectCode(), student.getExamNumber(),
-                        student.getSheetCount()));
+        student.setSheetUrls(PictureUrlBuilder.getSheetUrls(student.getExamId(), campus.getId(),
+                student.getSubjectCode(), student.getExamNumber(), student.getSheetCount()));
     }
 
     private void buildPackageUrl(ExamStudent student) {
         if (StringUtils.isNotBlank(student.getPackageCode())) {
             ExamPackage ep = packageService.find(student.getExamId(), student.getPackageCode());
             if (ep != null && ep.getPicCount() > 0) {
-                student.setPackageUrls(PictureUrlBuilder.getPackageUrls(student.getExamId(), student.getPackageCode(), ep.getPicCount()));
+                student.setPackageUrls(PictureUrlBuilder.getPackageUrls(student.getExamId(), student.getPackageCode(),
+                        ep.getPicCount()));
             }
         }
     }

+ 50 - 14
stmms-web/src/main/java/cn/com/qmth/stmms/admin/exam/TrialController.java

@@ -1,6 +1,7 @@
 package cn.com.qmth.stmms.admin.exam;
 
 import java.text.DecimalFormat;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
@@ -16,11 +17,14 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.ResponseBody;
 
+import cn.com.qmth.stmms.biz.exam.model.Exam;
+import cn.com.qmth.stmms.biz.exam.model.ExamQuestion;
 import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
 import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
 import cn.com.qmth.stmms.biz.exam.model.MarkGroup;
 import cn.com.qmth.stmms.biz.exam.model.Marker;
 import cn.com.qmth.stmms.biz.exam.service.ExamQuestionService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.biz.exam.service.MarkGroupService;
 import cn.com.qmth.stmms.biz.exam.service.MarkerService;
@@ -32,6 +36,7 @@ import cn.com.qmth.stmms.biz.mark.service.MarkService;
 import cn.com.qmth.stmms.biz.mark.service.TrialService;
 import cn.com.qmth.stmms.common.annotation.RoleRequire;
 import cn.com.qmth.stmms.common.domain.WebUser;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.enums.LibraryStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkStatus;
@@ -69,9 +74,15 @@ public class TrialController extends BaseExamController {
     @Autowired
     private ExamQuestionService questionService;
 
+    @Autowired
+    private ExamService examService;
+
     @Value("${slice.image.server}")
     private String sliceServer;
 
+    @Value("${json.server}")
+    private String jsonServer;
+
     @RequestMapping
     public String list(Model model, HttpServletRequest request, TrialLibrarySearchQuery query,
             @RequestParam(required = false) LibraryStatus status) {
@@ -86,7 +97,8 @@ public class TrialController extends BaseExamController {
             query.setSubjectCode(subjectList.get(0).getCode());
         }
         subjectFilter(query, wu);
-        List<MarkGroup> groupList = groupService.findByExamAndSubjectAndStatus(examId, query.getSubjectCode(), MarkStatus.TRIAL);
+        List<MarkGroup> groupList = groupService.findByExamAndSubjectAndStatus(examId, query.getSubjectCode(),
+                MarkStatus.TRIAL);
         if (!groupList.isEmpty()) {
             if (query.getGroupNumber() == null) {
                 query.setGroupNumber(groupList.get(0).getNumber());
@@ -97,10 +109,11 @@ public class TrialController extends BaseExamController {
                 library.setMarkCount(trialService.countHistory(library.getId()));
             }
             for (MarkGroup group : groupList) {
-                group.setQuestionList(questionService
-                        .findByExamAndSubjectAndObjectiveAndGroupNumber(examId, group.getSubjectCode(), false, group.getNumber()));
+                group.setQuestionList(questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(examId,
+                        group.getSubjectCode(), false, group.getNumber()));
             }
-            model.addAttribute("group", groupService.findOne(query.getExamId(), query.getSubjectCode(), query.getGroupNumber()));
+            model.addAttribute("group",
+                    groupService.findOne(query.getExamId(), query.getSubjectCode(), query.getGroupNumber()));
         }
         model.addAttribute("query", query);
         model.addAttribute("subjectList", getExamSubject(examId, wu));
@@ -116,9 +129,11 @@ public class TrialController extends BaseExamController {
         int examId = getSessionExamId(request);
         TrialLibrary library = trialService.findLibrary(libraryId);
         if (library != null) {
-            if (library.getExamId().equals(examId) && subjectCheck(library.getSubjectCode(), RequestUtils.getWebUser(request))) {
+            if (library.getExamId().equals(examId)
+                    && subjectCheck(library.getSubjectCode(), RequestUtils.getWebUser(request))) {
                 try {
-                    lockService.watch(LockType.GROUP, library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
+                    lockService.watch(LockType.GROUP, library.getExamId(), library.getSubjectCode(),
+                            library.getGroupNumber());
                     lockService.waitlock(LockType.TRIAL_LIBRARY, library.getId());
                     if (markService.resetLibrary(library)) {
                         obj.accumulate("success", true);
@@ -132,7 +147,8 @@ public class TrialController extends BaseExamController {
                     obj.accumulate("message", "无法打回该评卷任务");
                 } finally {
                     lockService.unlock(LockType.TRIAL_LIBRARY, library.getId());
-                    lockService.unwatch(LockType.GROUP, library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
+                    lockService.unwatch(LockType.GROUP, library.getExamId(), library.getSubjectCode(),
+                            library.getGroupNumber());
                 }
             } else {
                 obj.accumulate("success", false);
@@ -151,17 +167,37 @@ public class TrialController extends BaseExamController {
     public JSONObject detail(HttpServletRequest request, @RequestParam Integer libraryId) {
         JSONObject obj = new JSONObject();
         TrialLibrary library = trialService.findLibrary(libraryId);
+        Exam exam = examService.findById(library.getExamId());
         if (library != null) {
             ExamStudent student = studentService.findById(library.getStudentId());
-            MarkGroup group = groupService.findOne(library.getExamId(), library.getSubjectCode(), library.getGroupNumber());
+            MarkGroup group = groupService.findOne(library.getExamId(), library.getSubjectCode(),
+                    library.getGroupNumber());
             if (group != null && student != null) {
                 obj.accumulate("success", true);
-                // 裁切图配置
-                obj.accumulate("imageServer", sliceServer);
-                obj.accumulate("urls", PictureUrlBuilder
-                        .getSliceUrls(library.getExamId(), library.getCampusId(), library.getSubjectCode(), library.getExamNumber(),
-                                student.getSliceCount()));
-                obj.accumulate("pictureConfig", group.getPictureConfigList());
+                if (ExamType.MULTI_MEDIA.equals(exam.getType())) {
+                    // 多媒体配置
+                    obj.accumulate("jsonServer", jsonServer);
+                    obj.accumulate(
+                            "urls",
+                            PictureUrlBuilder.getAnswerJson(library.getExamId(), library.getSubjectCode(),
+                                    student.getPaperType(), library.getExamNumber()));
+                    List<ExamQuestion> questions = questionService.findByExamAndSubjectAndObjectiveAndGroupNumber(
+                            library.getExamId(), student.getSubjectCode(), false, group.getNumber());
+                    List<String> strings = new ArrayList<String>();
+                    for (ExamQuestion examQuestion : questions) {
+                        strings.add(examQuestion.getQuestionNumber());
+                    }
+                    String questionNumbers = String.join(",", strings);
+                    obj.accumulate("questionNumbers", questionNumbers);
+                } else {
+                    // 裁切图配置
+                    obj.accumulate("imageServer", sliceServer);
+                    obj.accumulate(
+                            "urls",
+                            PictureUrlBuilder.getSliceUrls(library.getExamId(), library.getCampusId(),
+                                    library.getSubjectCode(), library.getExamNumber(), student.getSliceCount()));
+                    obj.accumulate("pictureConfig", group.getPictureConfigList());
+                }
                 // 评卷记录集合
                 JSONArray array = new JSONArray();
                 List<TrialHistory> list = trialService.findHistory(libraryId);

+ 210 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/DataSyncThread.java

@@ -0,0 +1,210 @@
+package cn.com.qmth.stmms.admin.thread;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import cn.com.qmth.stmms.admin.utils.HttpUtil;
+import cn.com.qmth.stmms.admin.utils.UpyunConfig;
+import cn.com.qmth.stmms.biz.exam.model.DataSync;
+import cn.com.qmth.stmms.biz.exam.model.Exam;
+import cn.com.qmth.stmms.biz.exam.model.ExamStudent;
+import cn.com.qmth.stmms.biz.exam.model.ExamSubject;
+import cn.com.qmth.stmms.biz.exam.service.DataSyncService;
+import cn.com.qmth.stmms.biz.exam.service.ExamService;
+import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
+import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
+import cn.com.qmth.stmms.biz.lock.LockService;
+import cn.com.qmth.stmms.common.enums.LockType;
+import cn.com.qmth.stmms.common.utils.PictureUrlBuilder;
+
+public class DataSyncThread implements Runnable {
+
+    protected static Logger log = LoggerFactory.getLogger(DataSyncThread.class);
+
+    private DataSync dataSync;
+
+    private ExamStudentService studentService;
+
+    private ExamSubjectService subjectService;
+
+    private ExamService examService;
+
+    private LockService lockService;
+
+    private DataSyncService dataSyncService;
+
+    private Integer pageSize;
+
+    private String baseDir;
+
+    private UpyunConfig config;
+
+    private HttpUtil subjectHttp;
+
+    private HttpUtil studentHttp;
+
+    public static final String PAPER_STRUCT = "paperStructList";
+
+    public static final String SUBJECT_CODE = "subjectCode";
+
+    public static final String SUBJECT_NAME = "subjectName";
+
+    public static final String DATA_LIST = "dataList";
+
+    public static final String START_ID = "startId";
+
+    public static final String NEXT_ID = "nextId";
+
+    public static final String DEFAULT_NULL = "#";
+
+    public static final String SIZE = "size";
+
+    public DataSyncThread(DataSync dataSync, Integer pageSize, String baseDir, UpyunConfig config,
+            LockService lockService, DataSyncService dataSyncService, ExamService examService,
+            ExamStudentService studentService, ExamSubjectService subjectService) {
+        this.dataSync = dataSync;
+        this.lockService = lockService;
+        this.dataSyncService = dataSyncService;
+        this.examService = examService;
+        this.studentService = studentService;
+        this.subjectService = subjectService;
+        this.pageSize = pageSize;
+        this.baseDir = baseDir;
+        this.config = config;
+        this.subjectHttp = new HttpUtil(dataSync.getSubjectUrl(), dataSync.getSecretKey(), dataSync.getAppId(),
+                dataSync.getRootOrgId());
+        this.studentHttp = new HttpUtil(dataSync.getStudentUrl(), dataSync.getSecretKey(), dataSync.getAppId(),
+                dataSync.getRootOrgId());
+    }
+
+    @Override
+    public void run() {
+        log.info("start data sync for examId=" + dataSync.getExamId());
+        try {
+            // 获取考试信息
+            Exam exam = examService.findById(dataSync.getExamId());
+            dataSync.setSchoolId(exam.getSchoolId());
+            DataSync sync = dataSyncService.findByExamId(dataSync.getExamId());
+            if (sync == null) {
+                sync = dataSync;
+                sync.setCreateTime(new Date());
+                dataSyncService.save(sync);
+            }
+            JSONObject datas = new JSONObject();
+            datas.accumulate("examId", sync.getCloudExamId());
+            String subjectResult = subjectHttp.httpAction(null, datas.toString());
+            JSONObject subjectJson = JSONObject.fromObject(subjectResult);
+            JSONArray subjectArray = subjectJson.getJSONArray(PAPER_STRUCT);
+            for (int i = 0; i < subjectArray.size(); i++) {
+                JSONObject subject = subjectArray.getJSONObject(i);
+                String subjectCode = subject.getString(SUBJECT_CODE);
+                String subjectName = subject.getString(SUBJECT_NAME);
+                Long startId = 0L;
+                ExamSubject examSubject = subjectService.find(exam.getId(), subjectCode);
+                if (examSubject != null && !subjectCode.equals(sync.getSubjectCode())) {
+                    continue;
+                }
+                if (null != sync.getNextId()) {
+                    startId = sync.getNextId();
+                }
+                while (startId != null) {
+                    datas.put(SUBJECT_CODE, subjectCode);
+                    datas.put(START_ID, startId);
+                    datas.put(SIZE, pageSize);
+                    String studentResult = studentHttp.httpAction(null, datas.toString());
+
+                    JSONObject studentJson = JSONObject.fromObject(studentResult);
+
+                    Long nextId = studentJson.getLong(NEXT_ID);
+                    if (startId - nextId == 0) {
+                        startId = null;
+                    } else {
+                        startId = nextId;
+                    }
+                    Object obj = studentJson.get(DATA_LIST);
+                    if (obj instanceof JSONObject && ((JSONObject) obj).isNullObject()) {
+                        sync.setUpdateTime(new Date());
+                        sync.setNextId(startId);
+                        dataSyncService.save(sync);
+                        continue;
+                    }
+                    JSONArray studentArray = studentJson.getJSONArray(DATA_LIST);
+                    List<ExamStudent> list = new ArrayList<ExamStudent>();
+                    Date now = new Date();
+                    for (int j = 0; j < studentArray.size(); j++) {
+                        JSONObject student = studentArray.getJSONObject(j);
+                        ExamStudent examStudent = new ExamStudent();
+                        examStudent.setExamId(sync.getExamId());
+
+                        examStudent.setStudentCode(student.getString("studentCode"));
+                        examStudent.setName(student.getString("name"));
+                        examStudent.setCollege(DEFAULT_NULL);
+                        examStudent.setClassName(DEFAULT_NULL);
+                        examStudent.setTeacher(DEFAULT_NULL);
+                        examStudent.setCampusName(DEFAULT_NULL);
+                        examStudent.setSubjectCode(subjectCode);
+                        examStudent.setSubjectName(subjectName);
+                        examStudent.setExamNumber(student.getString("examNumber"));
+                        examStudent.setPaperType(null);
+
+                        examStudent.setSchoolId(exam.getSchoolId());
+                        examStudent.setAbsent(false);
+                        examStudent.setUpload(true);
+                        examStudent.setManualAbsent(false);
+                        examStudent.setBreach(false);
+                        examStudent.setException(false);
+                        examStudent.setSliceCount(0);
+                        examStudent.setSheetCount(0);
+                        examStudent.setObjectiveScore(0d);
+                        examStudent.setSubjectiveScore(0d);
+                        examStudent.setAnswers(null);
+                        examStudent.setBatchCode(null);
+                        examStudent.setUploadTime(now);
+                        list.add(examStudent);
+
+                        String answerJson = student.getString("subjectives");
+                        File file = new File(new File(baseDir, config.getMediaBucket()),
+                                PictureUrlBuilder.getAnswerJson(exam.getId(), subjectCode, null,
+                                        examStudent.getExamNumber()));
+                        if (!file.exists()) {
+                            file.getParentFile().mkdirs();
+                            file.createNewFile();
+                        }
+                        FileOutputStream fos = new FileOutputStream(file);
+                        fos.write(new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF });
+                        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));
+                        bw.write(answerJson);
+                        bw.close();
+                    }
+                    int count = studentService.batchSave(list);
+                    if (count > 0) {
+                        subjectService.updateUploadCount(exam.getId(), subjectCode,
+                                (int) studentService.countUploadedByExamIdAndSubjectCode(exam.getId(), subjectCode));
+                    }
+                    sync.setUpdateTime(new Date());
+                    sync.setNextId(startId);
+                    sync.setSubjectCode(subjectCode);
+                    dataSyncService.save(sync);
+                }
+            }
+            sync.setFinished(true);
+            dataSyncService.save(sync);
+        } catch (Exception e) {
+            log.error("data sync exception for examId=" + dataSync.getExamId(), e);
+        } finally {
+            lockService.unlock(LockType.DATA_SYNC, dataSync.getExamId());
+            log.info("finish data sync for examId=" + dataSync.getExamId());
+        }
+    }
+}

+ 2 - 2
stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/ScoreCheckThread.java

@@ -94,7 +94,7 @@ public class ScoreCheckThread implements Runnable {
             // question.setFullCount(question.getFullCount() + 1);
             // }
             // 统计考生总数
-            question.setTotalCount(question.getTotalCount() + 1);
+            // question.setTotalCount(question.getTotalCount() + 1);
         }
     }
 
@@ -107,7 +107,7 @@ public class ScoreCheckThread implements Runnable {
             }
             // 从数据库读取后,首先将人数清零
             for (ExamQuestion q : list) {
-                q.setTotalCount(0);
+                // q.setTotalCount(0);
                 // q.setZeroCount(0);
                 // q.setFullCount(0);
             }

+ 169 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/thread/SubjectScoreCalculateThread.java

@@ -0,0 +1,169 @@
+package cn.com.qmth.stmms.admin.thread;
+
+import cn.com.qmth.stmms.biz.exam.model.*;
+import cn.com.qmth.stmms.biz.exam.query.ExamStudentSearchQuery;
+import cn.com.qmth.stmms.biz.exam.service.*;
+import cn.com.qmth.stmms.biz.lock.LockService;
+import cn.com.qmth.stmms.biz.mark.service.MarkService;
+import cn.com.qmth.stmms.biz.report.service.ReportService;
+import cn.com.qmth.stmms.biz.report.utils.ReportContext;
+import cn.com.qmth.stmms.biz.utils.ScoreCalculateUtil;
+import cn.com.qmth.stmms.biz.utils.ScoreInfo;
+import cn.com.qmth.stmms.common.enums.LockType;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SubjectScoreCalculateThread implements Runnable {
+
+    protected static Logger log = LoggerFactory.getLogger(SubjectScoreCalculateThread.class);
+
+    private ExamStudentService studentService;
+
+    private ExamQuestionService questionService;
+
+    private MarkService markService;
+
+    private int examId;
+
+    private String subjectCode;
+
+    private Map<String, List<ExamQuestion>> objectiveMap;
+
+    private Map<String, List<ExamQuestion>> subjectiveMap;
+
+    private Map<String, ExamSubject> subjectMap;
+
+    private Map<String, List<MarkGroup>> groupMap;
+
+    private MarkGroupService groupService;
+
+    private ExamSubjectService subjectService;
+
+    private ExamService examService;
+
+    public SubjectScoreCalculateThread(int examId, String subjectCode, ExamStudentService studentService,
+            ExamQuestionService questionService, MarkService markService, ExamService examService, ExamSubjectService subjectService,
+            MarkGroupService groupService) {
+        this.examId = examId;
+        this.subjectCode = subjectCode;
+        this.studentService = studentService;
+        this.questionService = questionService;
+        this.markService = markService;
+        this.examService = examService;
+        this.subjectService = subjectService;
+        this.groupService = groupService;
+        this.objectiveMap = new HashMap<String, List<ExamQuestion>>();
+        this.subjectiveMap = new HashMap<String, List<ExamQuestion>>();
+        this.subjectMap = new HashMap<String, ExamSubject>();
+        this.groupMap = new HashMap<String, List<MarkGroup>>();
+    }
+
+    @Override
+    public void run() {
+        //log.info("start calculate for examId=" + examId);
+        try {
+            // 获取考试信息
+            // Exam exam = examService.findById(examId);
+            // 删除原有统计数据
+            // reportService.deleteData(examId);
+            // context = new ReportContext(exam);
+            // long totalCount = studentService.countByExamId(examId);
+
+            ExamStudentSearchQuery query = new ExamStudentSearchQuery();
+            query.setPageNumber(1);
+            query.setPageSize(1000);
+            query.setExamId(examId);
+            query.setSubjectCode(subjectCode);
+            query.setUpload(true);
+            List<ExamStudent> list = studentService.findByQuery(query).getResult();
+            while (list != null && list.size() > 0) {
+                for (ExamStudent student : list) {
+                    // 先统分
+                    calculate(student);
+                    // 后统计
+                    // statistic(student);
+                }
+                //double process = pageSize * pageNumber * 100.0 / totalCount;
+                //if (process >= 100) {
+                //    process = 99;
+                //}
+                //examService.updateProcess(examId, process);
+                query.setPageNumber(query.getPageNumber() + 1);
+                list = studentService.findByQuery(query).getResult();
+            }
+            // context.save();
+            // 统分结束修改标记
+            // examService.updateNeedCalculate(examId, false);
+            // examService.updateProcess(examId, null);
+        } catch (Exception e) {
+            //log.error("calculate exception for examId=" + examId, e);
+        } finally {
+            //lockService.unlock(LockType.SCORE_CALCULATE, examId);
+            //log.info("finish calculate for examId=" + examId);
+        }
+    }
+
+    private void calculate(ExamStudent student) {
+        // 未上传、缺考、违纪的考生不统分
+        if (!student.isUpload() || student.isAbsent() || student.isBreach()) {
+            return;
+        }
+        try {
+            ScoreCalculateUtil util = ScoreCalculateUtil.instance(student);
+            ScoreInfo info = util.calculate(findQuestionList(student.getSubjectCode(), student.getPaperType(), true), null);
+
+            student.setObjectiveScore(info.getObjectiveScore());
+            student.setScoreList(info.getScoreList(), true);
+
+            studentService.save(student);
+
+            // 增加主观题总分统计
+            markService.scoreCalculate(student, findMarkGroup(student.getSubjectCode()));
+
+        } catch (Exception e) {
+            log.error("calculate error for studentId=" + student.getId(), e);
+        }
+    }
+
+    private List<ExamQuestion> findQuestionList(String subjectCode, String paperType, boolean objective) {
+        if (objective) {
+            String key = subjectCode + "_" + StringUtils.trimToEmpty(paperType);
+            List<ExamQuestion> list = objectiveMap.get(key);
+            if (list == null) {
+                list = questionService.findByExamAndSubjectAndObjectiveAndPaperType(examId, subjectCode, true, paperType);
+                objectiveMap.put(key, list);
+            }
+            return list;
+        } else {
+            List<ExamQuestion> list = subjectiveMap.get(subjectCode);
+            if (list == null) {
+                list = questionService.findByExamAndSubjectAndObjective(examId, subjectCode, false);
+                subjectiveMap.put(subjectCode, list);
+            }
+            return list;
+        }
+    }
+
+    private ExamSubject findExamSubject(String subjectCode) {
+        ExamSubject subject = subjectMap.get(subjectCode);
+        if (subject == null) {
+            subject = subjectService.find(examId, subjectCode);
+            subjectMap.put(subjectCode, subject);
+        }
+        return subject;
+    }
+
+    private List<MarkGroup> findMarkGroup(String subjectCode) {
+        List<MarkGroup> list = groupMap.get(subjectCode);
+        if (list == null) {
+            list = groupService.findByExamAndSubject(examId, subjectCode);
+            groupMap.put(subjectCode, list);
+        }
+        return list;
+    }
+}

+ 155 - 156
stmms-web/src/main/java/cn/com/qmth/stmms/admin/user/UserController.java

@@ -1,156 +1,155 @@
-package cn.com.qmth.stmms.admin.user;
-
-import java.util.Date;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.commons.lang.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.servlet.ModelAndView;
-import org.springframework.web.servlet.mvc.support.RedirectAttributes;
-
-import cn.com.qmth.stmms.biz.user.model.User;
-import cn.com.qmth.stmms.biz.user.service.UserService;
-import cn.com.qmth.stmms.biz.user.service.query.UserSearchQuery;
-import cn.com.qmth.stmms.common.annotation.RoleRequire;
-import cn.com.qmth.stmms.common.controller.BaseController;
-import cn.com.qmth.stmms.common.enums.Role;
-import cn.com.qmth.stmms.common.utils.Md5EncryptUtils;
-import cn.com.qmth.stmms.common.utils.RequestUtils;
-
-@Controller
-@RequestMapping("/admin/user")
-public class UserController extends BaseController {
-
-    public static Role[] roleList = { Role.SCANNER, Role.SUBJECT_HEADER };
-
-    @Autowired
-    private UserService userService;
-
-    @RequestMapping("/list")
-    @RoleRequire(Role.SCHOOL_ADMIN)
-    public ModelAndView list(HttpServletRequest request, UserSearchQuery query) {
-        User user = RequestUtils.getWebUser(request).getUser();
-        query.setSchoolId(user.getSchoolId());
-        query = userService.findByQuery(query);
-
-        ModelAndView view = new ModelAndView("modules/user/userList");
-        view.addObject("roleList", roleList);
-        view.addObject("query", query);
-        return view;
-    }
-
-    @RequestMapping("/delete")
-    @RoleRequire(Role.SCHOOL_ADMIN)
-    public String delete(HttpServletRequest request, RedirectAttributes redirectAttributes, @RequestParam Integer id) {
-        User current = RequestUtils.getWebUser(request).getUser();
-        User user = userService.findById(id);
-        String message = null;
-        if (user == null) {
-            message = "找不到指定用户";
-        } else if (!user.getSchoolId().equals(current.getSchoolId()) || user.getId().equals(current.getId())) {
-            message = "不能删除指定用户";
-        } else {
-            userService.delete(user);
-            message = "删除成功";
-        }
-        redirectAttributes.addFlashAttribute("message", message);
-        return "redirect:/admin/user/list";
-    }
-
-    @RequestMapping(value = "/add", method = RequestMethod.GET)
-    @RoleRequire(Role.SCHOOL_ADMIN)
-    public ModelAndView addInit(HttpServletRequest request) {
-        ModelAndView view = new ModelAndView("modules/user/userEdit");
-        User user = new User();
-        user.setEnable(true);
-        view.addObject("user", user);
-        view.addObject("roleList", roleList);
-        return view;
-    }
-
-    @RequestMapping(value = "/edit", method = RequestMethod.GET)
-    @RoleRequire(Role.SCHOOL_ADMIN)
-    public ModelAndView editInit(HttpServletRequest request, @RequestParam Integer id) {
-        User current = RequestUtils.getWebUser(request).getUser();
-        User user = userService.findById(id);
-        if (user != null && user.getSchoolId().equals(current.getSchoolId())) {
-            ModelAndView view = new ModelAndView("modules/user/userEdit");
-            view.addObject("user", user);
-            view.addObject("roleList", roleList);
-            return view;
-        } else {
-            return new ModelAndView("redirect:/admin/user/list");
-        }
-    }
-
-    @RequestMapping(value = "/save", method = RequestMethod.POST)
-    @RoleRequire(Role.SCHOOL_ADMIN)
-    public String save(HttpServletRequest request, Model model, User user) {
-        User current = RequestUtils.getWebUser(request).getUser();
-        User previous = null;
-        if (user.getId() != null) {
-            previous = userService.findById(user.getId());
-        }
-        if (previous == null) {
-            String message = validate(user);
-            if (message == null) {
-                user.setPassword(Md5EncryptUtils.md5(user.getPassword()));
-                user.setSchoolId(current.getSchoolId());
-                user.setEnable(true);
-                user.setCreatedTime(new Date());
-                userService.save(user);
-            } else {
-                model.addAttribute("user", user);
-                model.addAttribute("roleList", roleList);
-                model.addAttribute("message", message);
-                return "modules/user/userEdit";
-            }
-        } else if (previous.getSchoolId().equals(current.getSchoolId())) {
-            previous.setLoginName(user.getLoginName());
-            previous.setName(user.getName());
-            previous.setRole(user.getRole());
-            if (StringUtils.isNotBlank(user.getPassword())) {
-                previous.setPassword(Md5EncryptUtils.md5(user.getPassword()));
-            }
-            if (previous.getRole() != Role.SUBJECT_HEADER) {
-                previous.setSubjectCode(null);
-            } else {
-                previous.setSubjectCode(user.getSubjectCode());
-            }
-            previous.setEnable(user.isEnable());
-            String message = validate(previous);
-            if (message == null) {
-                previous.setUpdatedTime(new Date());
-                userService.save(previous);
-            } else {
-                model.addAttribute("user", previous);
-                model.addAttribute("roleList", roleList);
-                model.addAttribute("message", message);
-                return "modules/user/userEdit";
-            }
-        }
-        return "redirect:/admin/user/list";
-    }
-
-    private String validate(User user) {
-        String message = null;
-        if (StringUtils.isBlank(user.getLoginName())) {
-            message = "登录名不能为空";
-        } else if (!checkLoginName(user)) {
-            message = "登录名不能重复";
-        } else if (StringUtils.isBlank(user.getName())) {
-            message = "名称不能为空";
-        } else if (StringUtils.isBlank(user.getPassword())) {
-            message = "密码不能为空";
-        } else if (user.getRole() == Role.SUBJECT_HEADER && StringUtils.isBlank(user.getSubjectCode())) {
-            message = "科组长必须绑定科目代码";
-        }
-        return message;
-    }
-}
+package cn.com.qmth.stmms.admin.user;
+
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.lang.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.servlet.ModelAndView;
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
+
+import cn.com.qmth.stmms.biz.user.model.User;
+import cn.com.qmth.stmms.biz.user.service.UserService;
+import cn.com.qmth.stmms.biz.user.service.query.UserSearchQuery;
+import cn.com.qmth.stmms.common.annotation.RoleRequire;
+import cn.com.qmth.stmms.common.controller.BaseController;
+import cn.com.qmth.stmms.common.enums.Role;
+import cn.com.qmth.stmms.common.utils.Md5EncryptUtils;
+import cn.com.qmth.stmms.common.utils.RequestUtils;
+
+@Controller
+@RequestMapping("/admin/user")
+public class UserController extends BaseController {
+
+    public static Role[] roleList = { Role.SCANNER, Role.SUBJECT_HEADER };
+
+    @Autowired
+    private UserService userService;
+
+    @RequestMapping("/list")
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public ModelAndView list(HttpServletRequest request, UserSearchQuery query) {
+        User user = RequestUtils.getWebUser(request).getUser();
+        query.setSchoolId(user.getSchoolId());
+        query = userService.findByQuery(query);
+
+        ModelAndView view = new ModelAndView("modules/user/userList");
+        view.addObject("roleList", roleList);
+        view.addObject("query", query);
+        return view;
+    }
+
+    @RequestMapping("/delete")
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public String delete(HttpServletRequest request, RedirectAttributes redirectAttributes, @RequestParam Integer id) {
+        User current = RequestUtils.getWebUser(request).getUser();
+        User user = userService.findById(id);
+        String message = null;
+        if (user == null) {
+            message = "找不到指定用户";
+        } else if (!user.getSchoolId().equals(current.getSchoolId()) || user.getId().equals(current.getId())) {
+            message = "不能删除指定用户";
+        } else {
+            userService.delete(user);
+            message = "删除成功";
+        }
+        redirectAttributes.addFlashAttribute("message", message);
+        return "redirect:/admin/user/list";
+    }
+
+    @RequestMapping(value = "/add", method = RequestMethod.GET)
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public ModelAndView addInit(HttpServletRequest request) {
+        ModelAndView view = new ModelAndView("modules/user/userEdit");
+        User user = new User();
+        user.setEnable(true);
+        view.addObject("user", user);
+        view.addObject("roleList", roleList);
+        return view;
+    }
+
+    @RequestMapping(value = "/edit", method = RequestMethod.GET)
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public ModelAndView editInit(HttpServletRequest request, @RequestParam Integer id) {
+        User current = RequestUtils.getWebUser(request).getUser();
+        User user = userService.findById(id);
+        if (user != null && user.getSchoolId().equals(current.getSchoolId())) {
+            ModelAndView view = new ModelAndView("modules/user/userEdit");
+            view.addObject("user", user);
+            view.addObject("roleList", roleList);
+            return view;
+        } else {
+            return new ModelAndView("redirect:/admin/user/list");
+        }
+    }
+
+    @RequestMapping(value = "/save", method = RequestMethod.POST)
+    @RoleRequire(Role.SCHOOL_ADMIN)
+    public String save(HttpServletRequest request, Model model, User user) {
+        User current = RequestUtils.getWebUser(request).getUser();
+        User previous = null;
+        if (user.getId() != null) {
+            previous = userService.findById(user.getId());
+        }
+        if (previous == null) {
+            String message = validate(user);
+            if (message == null) {
+                user.setPassword(Md5EncryptUtils.md5(user.getPassword()));
+                user.setSchoolId(current.getSchoolId());
+                user.setCreatedTime(new Date());
+                userService.save(user);
+            } else {
+                model.addAttribute("user", user);
+                model.addAttribute("roleList", roleList);
+                model.addAttribute("message", message);
+                return "modules/user/userEdit";
+            }
+        } else if (previous.getSchoolId().equals(current.getSchoolId())) {
+            previous.setLoginName(user.getLoginName());
+            previous.setName(user.getName());
+            previous.setRole(user.getRole());
+            if (StringUtils.isNotBlank(user.getPassword())) {
+                previous.setPassword(Md5EncryptUtils.md5(user.getPassword()));
+            }
+            if (previous.getRole() != Role.SUBJECT_HEADER) {
+                previous.setSubjectCode(null);
+            } else {
+                previous.setSubjectCode(user.getSubjectCode());
+            }
+            previous.setEnable(user.isEnable());
+            String message = validate(previous);
+            if (message == null) {
+                previous.setUpdatedTime(new Date());
+                userService.save(previous);
+            } else {
+                model.addAttribute("user", previous);
+                model.addAttribute("roleList", roleList);
+                model.addAttribute("message", message);
+                return "modules/user/userEdit";
+            }
+        }
+        return "redirect:/admin/user/list";
+    }
+
+    private String validate(User user) {
+        String message = null;
+        if (StringUtils.isBlank(user.getLoginName())) {
+            message = "登录名不能为空";
+        } else if (!checkLoginName(user)) {
+            message = "登录名不能重复";
+        } else if (StringUtils.isBlank(user.getName())) {
+            message = "名称不能为空";
+        } else if (StringUtils.isBlank(user.getPassword())) {
+            message = "密码不能为空";
+        } else if (user.getRole() == Role.SUBJECT_HEADER && StringUtils.isBlank(user.getSubjectCode())) {
+            message = "科组长必须绑定科目代码";
+        }
+        return message;
+    }
+}

+ 116 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/ByteUtil.java

@@ -0,0 +1,116 @@
+package cn.com.qmth.stmms.admin.utils;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+
+/**
+ * 字节转换工具
+ *
+ * @author WANGWEI
+ * @date 2018年4月27日
+ */
+public class ByteUtil {
+	public final static short UNSIGNED_MAX_VALUE = (Byte.MAX_VALUE * 2) + 1;
+
+	private ByteUtil() {
+	}
+
+	public static int unsignedPromote(byte b) {
+		return b & 0xff;
+	}
+
+	public static String toHexAscii(byte b) {
+		StringWriter sw = new StringWriter(2);
+		addHexAscii(b, sw);
+		return sw.toString();
+	}
+
+	public static String toLowercaseHexAscii(byte b) {
+		StringWriter sw = new StringWriter(2);
+		addLowercaseHexAscii(b, sw);
+		return sw.toString();
+	}
+
+	public static String toHexAscii(byte[] bytes) {
+		int len = bytes.length;
+		StringWriter sw = new StringWriter(len * 2);
+		for (int i = 0; i < len; ++i)
+			addHexAscii(bytes[i], sw);
+		return sw.toString();
+	}
+
+	public static String toLowercaseHexAscii(byte[] bytes) {
+		int len = bytes.length;
+		StringWriter sw = new StringWriter(len * 2);
+		for (int i = 0; i < len; ++i)
+			addLowercaseHexAscii(bytes[i], sw);
+		return sw.toString();
+	}
+
+	public static byte[] fromHexAscii(String s) throws NumberFormatException {
+		try {
+			int len = s.length();
+			if ((len % 2) != 0)
+				throw new NumberFormatException("Hex ascii must be exactly two digits per byte.");
+
+			int out_len = len / 2;
+			byte[] out = new byte[out_len];
+			int i = 0;
+			StringReader sr = new StringReader(s);
+			while (i < out_len) {
+				int val = (16 * fromHexDigit(sr.read())) + fromHexDigit(sr.read());
+				out[i++] = (byte) val;
+			}
+			return out;
+		} catch (IOException e) {
+			throw new InternalError("IOException reading from StringReader?!?!");
+		}
+	}
+
+	static void addHexAscii(byte b, StringWriter sw) {
+		int ub = unsignedPromote(b);
+		int h1 = ub / 16;
+		int h2 = ub % 16;
+		sw.write(toHexDigit(h1));
+		sw.write(toHexDigit(h2));
+	}
+
+	static void addLowercaseHexAscii(byte b, StringWriter sw) {
+		int ub = unsignedPromote(b);
+		int h1 = ub / 16;
+		int h2 = ub % 16;
+		sw.write(toLowercaseHexDigit(h1));
+		sw.write(toLowercaseHexDigit(h2));
+	}
+
+	private static int fromHexDigit(int c) throws NumberFormatException {
+		if (c >= 0x30 && c < 0x3A)
+			return c - 0x30;
+		else if (c >= 0x41 && c < 0x47)
+			return c - 0x37;
+		else if (c >= 0x61 && c < 0x67)
+			return c - 0x57;
+		else
+			throw new NumberFormatException('\'' + c + "' is not a valid hexadecimal digit.");
+	}
+
+	private static char toHexDigit(int h) {
+		char out;
+		if (h <= 9)
+			out = (char) (h + 0x30);
+		else
+			out = (char) (h + 0x37);
+		return out;
+	}
+
+	private static char toLowercaseHexDigit(int h) {
+		char out;
+		if (h <= 9)
+			out = (char) (h + 0x30);
+		else
+			out = (char) (h + 0x57);
+		return out;
+	}
+
+}

+ 210 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/HttpUtil.java

@@ -0,0 +1,210 @@
+package cn.com.qmth.stmms.admin.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Map;
+
+import net.sf.json.JSONObject;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class HttpUtil {
+
+    private static Logger log = LoggerFactory.getLogger(HttpUtil.class);
+
+    /** 默认的编码格式 */
+    public static final String DEFAULT_CHARSET = "UTF-8";
+
+    public static final String CONTENT_TYPE = "Content-Type";
+
+    public static final String APPLICATION_JSON = "application/json;charset=utf-8";
+
+    public static final String METHOD_POST = "POST";
+
+    public static final String ACCESS_TOKEN = "Access-Token";
+
+    public static final String TIMESTAMP = "timestamp";
+
+    public static final String ROOT_ORG_ID = "rootOrgId";
+
+    public static final String APP_ID = "appId";
+
+    // 请求地址
+    protected String uri = null;
+
+    protected String secretKey = null;
+
+    protected String appId = null;
+
+    protected String rootOrgId = null;
+
+    public HttpUtil(String uri, String secretKey, String appId, String rootOrgId) {
+        this.uri = uri;
+        this.secretKey = secretKey;
+        this.appId = appId;
+        this.rootOrgId = rootOrgId;
+    }
+
+    /**
+     * 
+     * @param params
+     *            headers参数
+     * @param datas
+     *            requestParams参数
+     * @return
+     */
+    public String httpAction(Map<String, String> params, String datas) {
+        String result = null;
+        HttpURLConnection conn = null;
+        OutputStream os = null;
+        InputStream is = null;
+
+        try {
+            // 获取链接
+            URL url = new URL(uri);
+            conn = (HttpURLConnection) url.openConnection();
+
+            long timestamp = System.currentTimeMillis();
+
+            StringBuilder sb = new StringBuilder();
+            sb.append(rootOrgId).append(appId).append(timestamp).append(secretKey);
+
+            byte[] bytes = SHA256.encode(sb.toString());
+            String accessToken = ByteUtil.toHexAscii(bytes);
+
+            conn.setRequestMethod(METHOD_POST);
+            conn.setRequestProperty(CONTENT_TYPE, APPLICATION_JSON);
+            // conn.setRequestProperty("accept", "application/json");
+            // 设置鉴权
+            conn.setRequestProperty(ACCESS_TOKEN, accessToken);
+            // 机构
+            conn.setRequestProperty(APP_ID, appId);
+            conn.setRequestProperty(ROOT_ORG_ID, rootOrgId);
+            // 设置时间
+            conn.setRequestProperty(TIMESTAMP, String.valueOf(timestamp));
+
+            conn.setUseCaches(false);
+            conn.setDoOutput(true);
+
+            // 设置额外的参数
+            if (params != null && !params.isEmpty()) {
+
+                for (Map.Entry<String, String> param : params.entrySet()) {
+                    conn.setRequestProperty(param.getKey(), param.getValue());
+                }
+            }
+            // 创建链接
+            conn.connect();
+            // 设置请求参数
+            if (datas != null) {
+                os = conn.getOutputStream();
+                os.write(datas.getBytes());
+                os.flush();
+            }
+
+            result = getResult(conn);
+        } catch (IOException e) {
+            log.error("Http connection error!", e);
+            e.printStackTrace();
+            // 操作失败
+            return null;
+        } finally {
+            try {
+                if (os != null) {
+                    os.close();
+                    os = null;
+                }
+                if (is != null) {
+                    is.close();
+                    is = null;
+                }
+            } catch (IOException e) {
+                log.error("Http connection error!", e);
+            }
+
+            if (conn != null) {
+                conn.disconnect();
+                conn = null;
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 获得连接请求的返回数据
+     * 
+     * @param conn
+     * 
+     * @return 字符串
+     */
+    private String getResult(HttpURLConnection conn) throws IOException {
+
+        StringBuilder text = new StringBuilder();
+
+        InputStream is = null;
+        InputStreamReader sr = null;
+        BufferedReader br = null;
+
+        int code = conn.getResponseCode();
+
+        try {
+            is = code >= 400 ? conn.getErrorStream() : conn.getInputStream();
+
+            sr = new InputStreamReader(is, DEFAULT_CHARSET);
+            br = new BufferedReader(sr);
+
+            char[] chars = new char[4096];
+            int length = 0;
+
+            while ((length = br.read(chars)) != -1) {
+                text.append(chars, 0, length);
+            }
+        } finally {
+            if (br != null) {
+                br.close();
+                br = null;
+            }
+            if (sr != null) {
+                sr.close();
+                sr = null;
+            }
+            if (is != null) {
+                is.close();
+                is = null;
+            }
+        }
+        if (code >= 400) {
+            throw new IOException(text.toString());
+        }
+        return text.toString();
+    }
+
+    public static void main(String[] args) {
+        JSONObject json = new JSONObject();
+        json.accumulate("examId", 672);
+
+        // HttpUtil subjectHttp = new HttpUtil(
+        // "http://iepcc-ps.ecs.qmth.com.cn:80/api/exchange/outer/question/getSubjectivePaperStruct",
+        // "Fu234234CK!fsd9f0", "EPCC", "16923");
+        // String subjectJson = subjectHttp.httpAction(null, json.toString());
+        // System.out.println(subjectJson);
+
+        long start = System.currentTimeMillis();
+        HttpUtil studentHttp = new HttpUtil(
+                "http://iepcc-ps.ecs.qmth.com.cn:80/api/exchange/outer/question/getSubjectiveQuestion",
+                "Fu234234CK!fsd9f0", "EPCC", "16923");
+        json.accumulate("subjectCode", "B149");
+        json.accumulate("startId", 0);
+        json.accumulate("size", 2);
+        String studentJson = studentHttp.httpAction(null, json.toString());
+        long end = System.currentTimeMillis();
+        System.out.println(end - start + ":" + studentJson);
+    }
+}

+ 25 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/SHA256.java

@@ -0,0 +1,25 @@
+package cn.com.qmth.stmms.admin.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * SHA256加密
+ * 
+ * @author WANGWEI
+ *
+ */
+public class SHA256 {
+
+	public static byte[] encode(String str) {
+		MessageDigest messageDigest;
+		try {
+			messageDigest = MessageDigest.getInstance("SHA-256");
+		} catch (NoSuchAlgorithmException e) {
+			throw new RuntimeException(e);
+		}
+		messageDigest.update(str.getBytes());
+		return messageDigest.digest();
+	}
+
+}

+ 8 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/utils/UpyunConfig.java

@@ -24,6 +24,9 @@ public class UpyunConfig {
     @Value("${upyun.slice.password}")
     private String slicePassword;
 
+    @Value("${upyun.media.bucket}")
+    private String mediaBucket;
+
     public String getSheetBucket() {
         return sheetBucket;
     }
@@ -47,4 +50,9 @@ public class UpyunConfig {
     public String getSlicePassword() {
         return slicePassword;
     }
+
+    public String getMediaBucket() {
+        return mediaBucket;
+    }
+
 }

+ 29 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/admin/vo/UploadStudentVO.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.stmms.admin.vo;
+
+import cn.com.qmth.stmms.common.annotation.ExcelField;
+
+public class UploadStudentVO {
+
+    @ExcelField(title = "准考证号", align = 2, sort = 10)
+    private String examNumber;
+
+    @ExcelField(title = "试卷类型", align = 2, sort = 20)
+    private String paperType;
+
+    public String getExamNumber() {
+        return examNumber;
+    }
+
+    public void setExamNumber(String examNumber) {
+        this.examNumber = examNumber;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+}

+ 8 - 7
stmms-web/src/main/java/cn/com/qmth/stmms/api/controller/ExamInfoController.java

@@ -5,10 +5,6 @@ import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 
-import cn.com.qmth.stmms.api.exception.ApiException;
-import cn.com.qmth.stmms.common.annotation.RoleRequire;
-import cn.com.qmth.stmms.common.domain.ApiUser;
-import cn.com.qmth.stmms.common.enums.Role;
 import net.sf.json.JSONArray;
 import net.sf.json.JSONObject;
 
@@ -21,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.ResponseBody;
 
 import cn.com.qmth.stmms.admin.vo.ExamSubjectVO;
+import cn.com.qmth.stmms.api.exception.ApiException;
 import cn.com.qmth.stmms.biz.campus.model.Campus;
 import cn.com.qmth.stmms.biz.campus.service.CampusService;
 import cn.com.qmth.stmms.biz.exam.model.Exam;
@@ -29,7 +26,10 @@ import cn.com.qmth.stmms.biz.exam.query.ExamSearchQuery;
 import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamSubjectService;
 import cn.com.qmth.stmms.biz.mark.model.PictureConfigItem;
+import cn.com.qmth.stmms.common.annotation.RoleRequire;
+import cn.com.qmth.stmms.common.domain.ApiUser;
 import cn.com.qmth.stmms.common.enums.ExamStatus;
+import cn.com.qmth.stmms.common.enums.Role;
 import cn.com.qmth.stmms.common.utils.DateUtils;
 import cn.com.qmth.stmms.common.utils.RequestUtils;
 
@@ -136,7 +136,8 @@ public class ExamInfoController extends BaseApiController {
     @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCANNER })
     @RequestMapping(value = "/sliceConfig/{examId}", method = RequestMethod.POST)
     @ResponseBody
-    public boolean updateSliceConfig(HttpServletRequest request, @PathVariable Integer examId, @RequestBody PictureConfigItem[] configs) {
+    public boolean updateSliceConfig(HttpServletRequest request, @PathVariable Integer examId,
+            @RequestBody PictureConfigItem[] configs) {
         ApiUser user = RequestUtils.getApiUser(request);
         Exam exam = examService.findById(examId);
         boolean success = false;
@@ -156,8 +157,8 @@ public class ExamInfoController extends BaseApiController {
     @RoleRequire({ Role.SCHOOL_ADMIN, Role.SCANNER })
     @RequestMapping(value = "/sliceConfig/{examId}/{subjectCode}", method = RequestMethod.POST)
     @ResponseBody
-    public boolean updateSubjectSliceConfig(HttpServletRequest request, @PathVariable Integer examId, @PathVariable String subjectCode,
-            @RequestBody PictureConfigItem[] configs) {
+    public boolean updateSubjectSliceConfig(HttpServletRequest request, @PathVariable Integer examId,
+            @PathVariable String subjectCode, @RequestBody PictureConfigItem[] configs) {
         ApiUser user = RequestUtils.getApiUser(request);
         Exam exam = examService.findById(examId);
         boolean success = false;

+ 5 - 2
stmms-web/src/main/java/cn/com/qmth/stmms/api/controller/ScanController.java

@@ -28,6 +28,7 @@ import cn.com.qmth.stmms.biz.exam.service.CheckStudentService;
 import cn.com.qmth.stmms.biz.exam.service.ExamService;
 import cn.com.qmth.stmms.biz.exam.service.ExamStudentService;
 import cn.com.qmth.stmms.common.enums.CheckType;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.utils.DateUtils;
 import cn.com.qmth.stmms.common.utils.RequestUtils;
 
@@ -63,7 +64,8 @@ public class ScanController extends BaseApiController {
         Exam exam = examService.findById(examId);
         JSONArray array = new JSONArray();
         // 判断上传权限
-        if (exam == null || !exam.getSchoolId().equals(user.getSchoolId())) {
+        if (exam == null || !exam.getSchoolId().equals(user.getSchoolId())
+                || !ExamType.SCAN_IMAGE.equals(exam.getType())) {
             throw ApiException.EXAM_NOT_ACCESSIBLED;
         }
         if (scStudentParameter != null && scStudentParameter.length > 0) {
@@ -85,7 +87,8 @@ public class ScanController extends BaseApiController {
                         array.add(obj);
                         // 增加人工审核数据
                         if (sc.isManual()) {
-                            checkStudentService.save(new CheckStudent(student.getId(), examId, student.getSubjectCode(), CheckType.MANUAL));
+                            checkStudentService.save(new CheckStudent(student.getId(), examId,
+                                    student.getSubjectCode(), CheckType.MANUAL));
                         }
                     }
                 }

+ 35 - 12
stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/BaseController.java

@@ -4,7 +4,6 @@ import java.beans.PropertyEditorSupport;
 import java.util.Date;
 import java.util.List;
 
-import cn.com.qmth.stmms.common.enums.*;
 import org.apache.commons.lang3.StringEscapeUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.propertyeditors.CustomBooleanEditor;
@@ -21,6 +20,15 @@ import cn.com.qmth.stmms.biz.user.model.User;
 import cn.com.qmth.stmms.biz.user.service.UserService;
 import cn.com.qmth.stmms.biz.utils.ScoreCalculateUtil;
 import cn.com.qmth.stmms.biz.utils.ScoreInfo;
+import cn.com.qmth.stmms.common.enums.CheckType;
+import cn.com.qmth.stmms.common.enums.ExamSubjectStatus;
+import cn.com.qmth.stmms.common.enums.ExamType;
+import cn.com.qmth.stmms.common.enums.HistoryStatus;
+import cn.com.qmth.stmms.common.enums.LibraryStatus;
+import cn.com.qmth.stmms.common.enums.MarkMode;
+import cn.com.qmth.stmms.common.enums.MarkStatus;
+import cn.com.qmth.stmms.common.enums.ObjectivePolicy;
+import cn.com.qmth.stmms.common.enums.Role;
 import cn.com.qmth.stmms.common.utils.DateUtils;
 
 public class BaseController {
@@ -174,22 +182,34 @@ public class BaseController {
                 }
             }
         });
+        // ExamType 类型转换
+        binder.registerCustomEditor(ExamType.class, new PropertyEditorSupport() {
+
+            @Override
+            public void setAsText(String text) {
+                try {
+                    setValue(ExamType.findByValue(Integer.valueOf(text)));
+                } catch (Exception e) {
+                    setValue(null);
+                }
+            }
+        });
 
         binder.registerCustomEditor(Boolean.class, new CustomBooleanEditor(true));
     }
 
     protected boolean saveUploadStudent(ExamStudent student) {
         ExamStudent old = studentService.findById(student.getId());
-        if (!student.isAbsent()) {//正考
-            List<MarkGroup> groupList = groupService
-                    .findByExamAndSubjectAndStatus(student.getExamId(), student.getSubjectCode(), MarkStatus.FINISH);
+        if (!student.isAbsent()) {// 正考
+            List<MarkGroup> groupList = groupService.findByExamAndSubjectAndStatus(student.getExamId(),
+                    student.getSubjectCode(), MarkStatus.FINISH);
             for (MarkGroup markGroup : groupList) {
-                groupService.updateStatus(student.getExamId(), student.getSubjectCode(), markGroup.getNumber(), MarkStatus.FORMAL,
-                        MarkStatus.FINISH);
+                groupService.updateStatus(student.getExamId(), student.getSubjectCode(), markGroup.getNumber(),
+                        MarkStatus.FORMAL, MarkStatus.FINISH);
             }
         }
         calculateObjectiveScore(student);
-        if (!old.isAbsent() && student.isAbsent()) {//正考转缺考
+        if (!old.isAbsent() && student.isAbsent()) {// 正考转缺考
             student.setObjectiveScore(0d);
             student.setObjectiveScoreList(null);
             student.setSubjectiveScore(0d);
@@ -198,8 +218,11 @@ public class BaseController {
         }
         boolean success = studentService.updateScanInfo(student);
         if (success) {
-            subjectService.updateUploadCount(student.getExamId(), student.getSubjectCode(),
-                    (int) studentService.countUploadedByExamIdAndSubjectCode(student.getExamId(), student.getSubjectCode()));
+            subjectService.updateUploadCount(
+                    student.getExamId(),
+                    student.getSubjectCode(),
+                    (int) studentService.countUploadedByExamIdAndSubjectCode(student.getExamId(),
+                            student.getSubjectCode()));
         }
         return success;
     }
@@ -207,9 +230,9 @@ public class BaseController {
     private void calculateObjectiveScore(ExamStudent student) {
         ScoreCalculateUtil util = ScoreCalculateUtil.instance(student);
 
-        ScoreInfo info = util.calculate(questionService
-                        .findByExamAndSubjectAndObjectiveAndPaperType(student.getExamId(), student.getSubjectCode(), true, student.getPaperType()),
-                null);
+        ScoreInfo info = util.calculate(
+                questionService.findByExamAndSubjectAndObjectiveAndPaperType(student.getExamId(),
+                        student.getSubjectCode(), true, student.getPaperType()), null);
 
         student.setObjectiveScore(info.getObjectiveScore());
         student.setScoreList(info.getScoreList(), true);

+ 13 - 12
stmms-web/src/main/java/cn/com/qmth/stmms/common/controller/LoginController.java

@@ -1,7 +1,6 @@
 package cn.com.qmth.stmms.common.controller;
 
 import java.util.Date;
-import java.util.UUID;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -75,27 +74,27 @@ public class LoginController {
 
                     session.saveWebUser(new WebUser(u));
 
-                    if (u.getRole() == Role.SYS_ADMIN || u.getRole() == Role.SCHOOL_ADMIN || u.getRole() == Role.SUBJECT_HEADER
-                            || u.getRole() == Role.SCHOOL_VIEWER) {
+                    if (u.getRole() == Role.SYS_ADMIN || u.getRole() == Role.SCHOOL_ADMIN
+                            || u.getRole() == Role.SUBJECT_HEADER || u.getRole() == Role.SCHOOL_VIEWER) {
                         ModelAndView modelAndView = new ModelAndView("redirect:admin/home");
                         return modelAndView;
                     } else {
                         ModelAndView view = new ModelAndView("modules/sys/login");
-                        view.addObject("message", "用户没有访问权限");
+                        view.addObject("message", "user.login.error.access");
                         view.addObject("showType", showType);
                         view.addObject("indexLogo", indexLogo);
                         return view;
                     }
                 } else {
                     ModelAndView modelAndView = new ModelAndView("modules/sys/login");
-                    modelAndView.addObject("message", "密码错误");
+                    modelAndView.addObject("message", "user.login.error.password");
                     modelAndView.addObject("showType", showType);
                     modelAndView.addObject("indexLogo", indexLogo);
                     return modelAndView;
                 }
             } else {
                 ModelAndView modelAndView = new ModelAndView("modules/sys/login");
-                modelAndView.addObject("message", "无此用户");
+                modelAndView.addObject("message", "user.login.error.account");
                 modelAndView.addObject("showType", showType);
                 modelAndView.addObject("indexLogo", indexLogo);
                 return modelAndView;
@@ -105,26 +104,27 @@ public class LoginController {
             if (marker != null) {
                 ModelAndView modelAndView = new ModelAndView("modules/sys/login");
                 if (!marker.getPassword().equals(user.getPassword())) {
-                    modelAndView.addObject("message", "密码错误");
+                    modelAndView.addObject("message", "user.login.error.password");
                     modelAndView.addObject("showType", showType);
                     modelAndView.addObject("indexLogo", indexLogo);
                     return modelAndView;
                 }
                 if (marker.isEnable() == false) {
-                    modelAndView.addObject("message", "帐号已禁用");
+                    modelAndView.addObject("message", "user.login.error.disabled");
                     modelAndView.addObject("showType", showType);
                     modelAndView.addObject("indexLogo", indexLogo);
                     return modelAndView;
                 }
-                MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+                MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(),
+                        marker.getGroupNumber());
                 if (group == null) {
-                    modelAndView.addObject("message", "大题不存在");
+                    modelAndView.addObject("message", "user.login.error.group");
                     modelAndView.addObject("showType", showType);
                     modelAndView.addObject("indexLogo", indexLogo);
                     return modelAndView;
                 }
                 if (group.getStatus() == MarkStatus.FINISH) {
-                    modelAndView.addObject("message", "评卷已结束");
+                    modelAndView.addObject("message", "user.login.error.finish");
                     modelAndView.addObject("showType", showType);
                     modelAndView.addObject("indexLogo", indexLogo);
                     return modelAndView;
@@ -144,7 +144,7 @@ public class LoginController {
             }
 
             ModelAndView view = new ModelAndView("modules/sys/login");
-            view.addObject("message", "帐号不存在");
+            view.addObject("message", "user.login.error.account");
             view.addObject("showType", showType);
             view.addObject("indexLogo", indexLogo);
             return view;
@@ -166,6 +166,7 @@ public class LoginController {
     /**
      * 登出
      *
+     * @param user
      * @param request
      * @param response
      * @return

+ 18 - 4
stmms-web/src/main/java/cn/com/qmth/stmms/mark/MarkController.java

@@ -48,6 +48,7 @@ import cn.com.qmth.stmms.biz.mark.service.ProblemTypeService;
 import cn.com.qmth.stmms.biz.mark.service.TaskService;
 import cn.com.qmth.stmms.biz.mark.service.TrialService;
 import cn.com.qmth.stmms.common.controller.BaseController;
+import cn.com.qmth.stmms.common.enums.ExamType;
 import cn.com.qmth.stmms.common.enums.LibraryStatus;
 import cn.com.qmth.stmms.common.enums.LockType;
 import cn.com.qmth.stmms.common.enums.MarkMode;
@@ -105,6 +106,9 @@ public class MarkController extends BaseController {
     @Value("${marker.forceMode}")
     private String forceMarkMode;
 
+    @Value("${json.server}")
+    private String jsonServer;
+
     @RequestMapping(value = "/reset", method = RequestMethod.GET)
     public ModelAndView reset(HttpServletRequest request) {
         Marker marker = RequestUtils.getWebUser(request).getMarker();
@@ -133,9 +137,18 @@ public class MarkController extends BaseController {
     }
 
     private ModelAndView getMarkModeView(Marker marker, MarkMode mode) {
+        // 多媒体阅卷
+        Exam exam = examService.findById(marker.getExamId());
         boolean forceMode = false;
         MarkMode sysMode = MarkMode.findByName(forceMarkMode);
         MarkGroup group = groupService.findOne(marker.getExamId(), marker.getSubjectCode(), marker.getGroupNumber());
+        if (ExamType.MULTI_MEDIA.equals(exam.getType())) {
+            ModelAndView view = new ModelAndView("modules/mark/markJson");
+            view.addObject("forceMode", false);
+            view.addObject("sheetView", false);
+            view.addObject("isFormal", group.getStatus() == MarkStatus.FORMAL);
+            return view;
+        }
         if (sysMode != null) {
             // 全局配置的强制评卷模式
             mode = sysMode;
@@ -186,6 +199,7 @@ public class MarkController extends BaseController {
         modelAndView.addObject("sliceServer", sliceServer);
         modelAndView.addObject("sheetServer", sheetServer);
         modelAndView.addObject("cardServer", cardServer);
+        modelAndView.addObject("jsonServer", jsonServer);
         modelAndView.addObject("marker", marker);
         ExamSubject subject = subjectService.find(marker.getExamId(), marker.getSubjectCode());
         modelAndView.addObject("subject", subject);
@@ -281,11 +295,11 @@ public class MarkController extends BaseController {
             if (group == null) {
                 task = new Task();
                 task.setExist(false);
-                task.setMessage("评卷大题不存在");
+                task.setMessage("mark.control.task.not.exist");
             } else if (group.getStatus() == MarkStatus.FINISH) {
                 task = new Task();
                 task.setExist(false);
-                task.setMessage("评卷已结束");
+                task.setMessage("mark.control.task.finish");
             } else if (group.getStatus() == MarkStatus.TRIAL) {
                 task = getTrialTask(marker);
             } else if (group.getStatus() == MarkStatus.FORMAL) {
@@ -294,7 +308,7 @@ public class MarkController extends BaseController {
             if (task == null) {
                 task = new Task();
                 task.setExist(false);
-                task.setMessage("当前无评卷任务");
+                task.setMessage("mark.control.task.null");
             }
         } catch (Exception e) {
             log.error("get task error", e);
@@ -375,7 +389,7 @@ public class MarkController extends BaseController {
         result.accumulate("success", success);
         result.accumulate("status", status(request));
         if (!success) {
-            result.accumulate("message", "评卷任务提交失败,请刷新页面");
+            result.accumulate("message", "mark.control.tsak.error");
         }
         return result;
     }

+ 59 - 0
stmms-web/src/main/java/cn/com/qmth/stmms/monitor/ScoreMonitorController.java

@@ -0,0 +1,59 @@
+package cn.com.qmth.stmms.monitor;
+
+import cn.com.qmth.stmms.admin.thread.ScoreCalculateThread;
+import cn.com.qmth.stmms.admin.thread.SubjectScoreCalculateThread;
+import cn.com.qmth.stmms.biz.exam.service.*;
+import cn.com.qmth.stmms.biz.mark.service.MarkService;
+import cn.com.qmth.stmms.biz.utils.FormalTaskUtil;
+import cn.com.qmth.stmms.biz.utils.TaskEntry;
+import cn.com.qmth.stmms.common.utils.DateUtils;
+import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+import java.util.Set;
+
+@Controller("scoreMonitor")
+@RequestMapping("/monitor/score")
+public class ScoreMonitorController {
+
+    @Autowired
+    private ExamSubjectService subjectService;
+
+    @Autowired
+    private ExamStudentService studentService;
+
+    @Autowired
+    private ExamQuestionService questionService;
+
+    @Autowired
+    private MarkService markService;
+
+    @Autowired
+    private ExamService examService;
+
+    @Autowired
+    private MarkGroupService groupService;
+
+    @Qualifier("task-executor")
+    @Autowired
+    private AsyncTaskExecutor taskExecutor;
+
+    @RequestMapping("/calculate")
+    @ResponseBody
+    public Object list(HttpServletRequest request, @RequestParam Integer examId, @RequestParam String subjectCode) {
+        SubjectScoreCalculateThread thread = new SubjectScoreCalculateThread(examId, subjectCode, studentService, questionService,
+                markService, examService, subjectService, groupService);
+        taskExecutor.submit(thread);
+        return true;
+    }
+
+}

+ 19 - 4
stmms-web/src/main/webapp/WEB-INF/application.properties

@@ -11,6 +11,7 @@ slice.image.server=https://ft-slice.markingcloud.com
 sheet.image.server=https://ft-sheet.markingcloud.com
 package.image.server=http://ft-package.markingcloud.com
 card.server=http://ft-card.markingcloud.com
+json.server=/file/ft-media
 ##slice.image.server=http://${local.ip}:9000/gx-slice
 ##sheet.image.server=http://${local.ip}:9000/gx-sheet
 ##package.image.server=http://${local.ip}:9000/gx-package
@@ -21,19 +22,22 @@ card.server=http://ft-card.markingcloud.com
 ##card.server=/file/ft-card
 
 ##file root path
-file.root=
-file.save=/Users/luoshi/develop/data/stmms-ft
+file.root=/Users/ting.yin/Desktop/static
+file.save=
 
 ##upyun image config
-upyun.sheet.bucket=
+upyun.sheet.bucket=ft-sheet
 upyun.sheet.username=
 upyun.sheet.password=
 
-upyun.slice.bucket=
+upyun.slice.bucket=ft-slice
 upyun.slice.username=
 upyun.slice.password=
 
+upyun.media.bucket=ft-media
+
 mark.cleanTimeoutMinute=20
+mark.activeExpireMinute=30
 mark.cleanTaskSchedule=0 0/10 6-23 * * ?
 mark.cleanLockSchedule=0 0 3 * * ?
 
@@ -41,3 +45,14 @@ marker.showBtnImportAndBtnUpdateImport=false
 marker.forceMode=
 ##\u9996\u9875\u53EF\u9009\u7684logo\u6587\u4EF6
 index.logo=
+
+##qmth.config
+qmth.examcloud.host=iepcc-ps.ecs.qmth.com.cn
+qmth.examcloud.port=80
+qmth.examcloud.rootOrgId=16923
+qmth.examcloud.appId=EPCC
+qmth.examcloud.secretKey=Fu234234CK!fsd9f0
+qmth.examcloud.pageSize=10
+qmth.examcloud.subject.uri=/api/exchange/outer/question/getSubjectivePaperStruct
+qmth.examcloud.student.uri=/api/exchange/outer/question/getSubjectiveQuestion
+

+ 2 - 1
stmms-web/src/main/webapp/WEB-INF/spring-mvc.xml

@@ -18,10 +18,11 @@
     <!-- Handles HTTP GET requests for /static/** by efficiently serving
         up static resources in the ${webappRoot}/static/ directory -->
     <mvc:resources mapping="/resources/**" location="/static/" order="0"/>
-    <mvc:resources mapping="/static/**" location="file:${app.home}/static/" order="0"/>
+    <mvc:resources mapping="/static/**" location="file:${file.root}/" order="0"/>
 
     <!-- 自动扫描包下的所有类,使其认为spring mvc的控制器 -->
     <context:component-scan base-package="cn.com.qmth.stmms.common.controller,
+    				  cn.com.qmth.stmms.common.authorization,
 					  cn.com.qmth.stmms.admin,
 					  cn.com.qmth.stmms.api,
 					  cn.com.qmth.stmms.file,

+ 2 - 1
stmms-web/src/main/webapp/WEB-INF/views/include/head.jsp

@@ -34,5 +34,6 @@
 <script src="${ctxStatic}/jquery-jbox/2.3/jquery.jBox-2.3.min.js" type="text/javascript"></script>
 <script src="${ctxStatic}/jquery-jbox/2.3/i18n/jquery.jBox-zh-CN.min.js" type="text/javascript"></script>
 <script src="${ctxStatic}/utils/image-utils.js" type="text/javascript"></script>
-
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
 <link rel="shortcut icon" href="${ctxStatic}/favicon.png">

+ 1 - 0
stmms-web/src/main/webapp/WEB-INF/views/include/taglib.jsp

@@ -1,6 +1,7 @@
 <%@ taglib prefix="shiro" uri="/WEB-INF/tlds/shiros.tld" %>
 <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
 <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
 <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
 <%@ taglib prefix="fns" uri="/WEB-INF/tlds/fns.tld" %>

+ 93 - 9
stmms-web/src/main/webapp/WEB-INF/views/include/trialDetail.jsp

@@ -1,6 +1,8 @@
 <%@ page contentType="text/html;charset=UTF-8"%>
 <link rel="stylesheet" type="text/css" href="${ctxStatic}/jBox/Source/jBox.css">
 <script type="text/javascript" src="${ctxStatic}/jBox/Source/jBox.min.js"></script>
+<link rel="stylesheet" type="text/css" href="${ctxStatic}/rich-text/css/rich-text.css">
+<script type="text/javascript" src="${ctxStatic}/rich-text/js/render.js"></script>
 
 <div id="trial-detail-content" class="container-fluid" style="display:none">
 <div class="row-fluid">
@@ -51,21 +53,103 @@ function initTrialDetailContent(libraryId){
 	                var history = data.historyList[i];
 	                $('#trial-history').append('<tr><td>'+history.loginName+'</td><td>'+history.name+'</td><td>'+history.score+'</td><td>'+history.time+'</td></tr>');
 	            }
-	            new ImageLoader( {
-	                server: data.imageServer
-	            }).merge(data.urls, data.pictureConfig, function(image){
-                    trialDetailModal.setContent($('#trial-detail-content'));
-                    $(image).width($('#trial-left-div').width()*0.95);
-                    $(image).appendTo('#trial-left-div');
-                }, function(error){
-	                trialDetailModal.setTitle('加载失败');
-	            });
+	            if(data.imageServer != undefined && data.pictureConfig != undefined){
+		            new ImageLoader( {
+		                server: data.imageServer
+		            }).merge(data.urls, data.pictureConfig, function(image){
+	                    trialDetailModal.setContent($('#trial-detail-content'));
+	                    $(image).width($('#trial-left-div').width()*0.95);
+	                    $(image).appendTo('#trial-left-div');
+	                }, function(error){
+		                trialDetailModal.setTitle('加载失败');
+		            });
+	            }else if(data.jsonServer != undefined && data.questionNumbers != undefined){
+	            	var url = data.jsonServer + data.urls;
+	            	var questionNumbers = data.questionNumbers.split(",");
+	            	let questionArr = {};
+	            	questionNumbers.forEach(questionNumber => {
+	            		questionArr[questionNumber] = true;
+	                });
+	            	$.get(url, function (result) {
+	            		 let questions = JSON.parse(result) || [];
+	            		//var result = '[{"mainNumber": 6,"subNumber": 19,"body": {"sections": [{ "blocks":[{"type": "text","value": "我是题目我是题目我是题目"}]}]},"answer": {"sections": [{"blocks":[{"type": "image","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/20/3_8_20_15748452889591137.jpeg"}]},{"blocks":[{"type": "audio","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/19/3_8_19_15748452552644264.mp3"}]}]},"studentAnswer": {"sections": [{"blocks":[{"type": "image","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/20/3_8_20_15748452889591137.jpeg"},{"type": "image","value": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582709869784&di=39683b1330bd09bebd93c95fe925ca70&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn10108%2F170%2Fw600h370%2F20190222%2Fd6de-htknpmh2595255.jpg"},{"type": "text","value": "我是答案答案daan"}]}]}}]';
+	            		//let questions = JSON.parse(result);
+
+	            		 questions.forEach(question => {
+	            	            // 按题号过滤
+	            	            if (questionArr[question.mainNumber + "." + question.subNumber] === true) {
+	            	                let numberTitle = {
+	            	                    "blocks": [{
+	            	                        "type": "text",
+	            	                        "param": {"bold": true},
+	            	                        "value": "题号:" + question.mainNumber + "-" + question.subNumber
+	            	                    }]
+	            	                };
+	            	                let bodyTitle = {
+	            	                    "blocks": [{
+	            	                        "type": "text",
+	            	                        "param": {"bold": true},
+	            	                        "value": "题干:"
+	            	                    }]
+	            	                };
+	            	                let answerTitle = {
+	            	                    "blocks": [{
+	            	                        "type": "text",
+	            	                        "param": {"bold": true},
+	            	                        "value": "考生答案:"
+	            	                    }, {
+	            	                        "type": "text",
+	            	                        "param": {
+	            	                            "italic": true,
+	            	                            "danger": true
+	            	                        },
+	            	                        "value": "(字数统计:" + textCount(question.studentAnswer) + ")"
+	            	                    }]
+	            	                };
+	            	                let standardTitle = {
+	            	                    "blocks": [{
+	            	                        "type": "text",
+	            	                        "param": {"bold": true},
+	            	                        "value": "标准答案:"
+	            	                    }]
+	            	                };
+	            	                this.holder = $('#trial-left-div');
+	            	                $(renderSection(numberTitle)).appendTo(this.holder);
+	            	                $(renderSection(bodyTitle)).appendTo(this.holder);
+	            	                if(question.parentBody!=null && question.parentBody!=undefined){
+	            						$(renderRichText(question.parentBody)).appendTo(this.holder); 
+	            					}
+	            	                $(renderRichText(question.body)).appendTo(this.holder);
+	            	                $(renderSection(answerTitle)).appendTo(this.holder);
+	            	                $(renderRichText(question.studentAnswer)).appendTo(this.holder);
+	            	                $(renderSection(standardTitle)).appendTo(this.holder);
+	            	                $(renderRichText(question.answer)).appendTo(this.holder);
+	            	                trialDetailModal.setContent($('#trial-detail-content'));
+	            	            }
+	            	        });
+	                }).error(function () {
+	                	trialDetailModal.setTitle('加载失败')
+	                }); 
+	            }
 	        }else {
 	            trialDetailModal.setTitle('加载失败')
 	        }
         }
     });
 }
+function textCount(jsonBody) {
+    let sections = jsonBody.sections || [];
+    let count = 0;
+    sections.forEach(section => {
+        let blocks = section.blocks || [];
+        blocks.forEach(block => {
+            if (block.type === 'text' && block.value != undefined) {
+                count += block.value.length;
+            }
+        });
+    });
+    return count;
+}
 </script>
 
 

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

@@ -19,6 +19,9 @@
 <script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
 <link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
 
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/mark-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/sheet-view.js"></script>

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

@@ -0,0 +1,86 @@
+<%@ page language="java" pageEncoding="utf-8"%>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>云阅卷高校版</title>
+<link href="${ctxStatic}/mark-new/css/bootstrap.css" rel="stylesheet" type="text/css" />
+<link href="${ctxStatic}/mark-new/css/style.css" rel="stylesheet" type="text/css" />
+
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/json2.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
+<link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css" rel="stylesheet" />
+
+<script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
+<link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
+
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+<script src="${ctxStatic}/rich-text/js/render.js"></script>
+<link href="${ctxStatic}/rich-text/css/rich-text.css" rel="stylesheet">
+<script type="text/javascript" src="${ctxStatic}/viewer/viewer.js"></script>
+<link href="${ctxStatic}/viewer/viewer.css" rel="stylesheet">
+
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/mark-control.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/json-view.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/json-loader.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/mark-board.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/mark-history.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/header-mark-status.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/warning-info.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/thumbnail.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/view-sidebar.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/arbitration-process.js"></script>
+</head>
+<body>
+	<div class="container-fluid" id="container"></div>
+	<script type="text/javascript">
+		$(document).ready(function() {
+			var mc = new MarkControl({
+				container : $('#container'),
+				staticServer : '${ctxStatic}',
+				imageServer : '${sliceServer}',
+				userName : '${web_user.name}',
+				logoutTitle: '关闭',
+				logoutUrl: 'javascript:window.close()',
+				clearUrl: '${ctx}/admin/exam/arbitrate/clear',
+				modules : {
+					'json-loader': {
+	                    jsonServer: '${jsonServer}'
+	                },
+	                'json-view': {},
+					'header-mark-status': {
+						title : '${subject.code}_${subject.name}_${group.title}'
+					},
+					'mark-history':{
+                        pageSize:10
+                    },
+					'mark-board' : {
+						showScoreBoard : false,
+						autoSubmit : false,
+						needConfirm : false
+					},
+					'arbitration-process': {
+					},
+					'warning-info': {
+					}
+				}
+			});
+			mc.start({
+				mode : 'loop',
+				statusUrl : '${ctx}/admin/exam/arbitrate/status?subjectCode=${group.subjectCode}&groupNumber=${group.number}',
+				getUrl : '${ctx}/admin/exam/arbitrate/getTask?subjectCode=${group.subjectCode}&groupNumber=${group.number}',
+				historyUrl : '${ctx}/admin/exam/arbitrate/history/${group.subjectCode}/${group.number}',
+				submitUrl : '${ctx}/admin/exam/arbitrate/saveTask',
+				clearUrl : '${ctx}/admin/exam/arbitrate/clear'
+			});
+		});
+	</script>
+</body>
+</html>

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

@@ -40,7 +40,7 @@
                 </c:forEach>
             </select>
             <label>准考证号</label>
-            <input type="text" name="examNumber" value="${query.examNumber}" maxlength="10" class="input-medium"/>
+            <input type="text" name="examNumber" value="${query.examNumber}" maxlength="20" class="input-medium"/>
 			&nbsp;
 			<input id="btnSubmit" class="btn btn-primary" type="button" value="查询" onclick="goSearch()"/>
 			

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

@@ -19,6 +19,9 @@
 <script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
 <link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
 
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/mark-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/sheet-view.js"></script>

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

@@ -0,0 +1,83 @@
+<%@ page language="java" pageEncoding="utf-8"%>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>云阅卷高校版</title>
+<link href="${ctxStatic}/mark-new/css/bootstrap.css" rel="stylesheet" type="text/css" />
+<link href="${ctxStatic}/mark-new/css/style.css" rel="stylesheet" type="text/css" />
+
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/json2.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
+<link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css" rel="stylesheet" />
+
+<script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
+<link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
+
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+<script src="${ctxStatic}/rich-text/js/render.js"></script>
+<link href="${ctxStatic}/rich-text/css/rich-text.css" rel="stylesheet">
+<script type="text/javascript" src="${ctxStatic}/viewer/viewer.js"></script>
+<link href="${ctxStatic}/viewer/viewer.css" rel="stylesheet">
+
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/mark-control.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/json-view.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/json-loader.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/mark-board.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/header-mark-status.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/warning-info.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/thumbnail.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/view-sidebar.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/arbitration-process.js"></script>
+</head>
+<body>
+	<div class="container-fluid" id="container"></div>
+	<script type="text/javascript">
+		$(document).ready(function() {
+			var mc = new MarkControl({
+				container : $('#container'),
+				staticServer : '${ctxStatic}',
+				imageServer : '${sliceServer}',
+				userName : '${web_user.name}',
+				logoutTitle: '关闭',
+				logoutUrl: 'javascript:window.close()',
+				submitUrl: '${ctx}/admin/exam/arbitrate/saveTask',
+				modules : {
+					'json-loader': {
+	                    jsonServer: '${jsonServer}'
+	                },
+	                'json-view': {},
+					'header-mark-status': {
+						title : '${subject.code}_${subject.name}_${group.title}'
+					},
+					'mark-board' : {
+						showScoreBoard : false,
+						autoSubmit : false,
+						needConfirm : false
+					},
+					'arbitration-process': {
+					},
+					'warning-info': {
+					}
+				}
+			});
+			mc.on('task.submit.success', this, function(event, context){
+                window.close();
+            });
+            mc.on('task.submit.error', this, function(event, context){
+                //window.close();
+            });
+            $.post('${ctx}/admin/exam/arbitrate/singleTask?historyId=${history.id}', {}, function(task){
+                mc.setTask(task);
+            });
+		});
+	</script>
+</body>
+</html>

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

@@ -0,0 +1,97 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<html>
+<head>
+	<title>数据同步</title>
+	<meta name="decorator" content="default"/>
+	<%@include file="/WEB-INF/views/include/head.jsp" %>
+	<script type="text/javascript">
+		$(document).ready(function() {
+			$("#inputForm").validate({
+				submitHandler: function(form){
+					loading('正在提交,请稍等...');
+					form.submit();
+				},
+				errorContainer: "#messageBox",
+				errorPlacement: function(error, element) {
+					$("#messageBox").text("输入有误,请先更正。");
+					if (element.is(":checkbox")||element.is(":radio")||element.parent().is(".input-append")){
+						error.appendTo(element.parent().parent());
+					} else {
+						error.insertAfter(element);
+					}
+				}
+			});
+		});
+	</script>
+</head>
+<body>
+	<ul class="nav nav-tabs">
+		<li><a href="${ctx}/admin/exam-list">考试列表</a></li>
+		<li class="active"><a href="##">数据同步</a></li>
+	</ul><br/>
+	<form:form id="inputForm" modelAttribute="dataSync" action="${ctx}/admin/exam/data/sync/save" method="post" class="form-horizontal">
+		<form:hidden path="examId"/>
+		<tags:message content="${message}"/>
+		<div class="control-group">
+			<label class="control-label">考试名称</label>
+			<div class="controls">
+				${exam.name}
+			</div>
+		</div>
+		<div class="control-group">
+			<label class="control-label">机构ID</label>
+			<div class="controls">
+				<input name="rootOrgId" value="${dataSync.rootOrgId }" class="required" <c:if test="${dataSync.cloudExamId!=null }">readonly</c:if>/>
+			</div>
+		</div>
+		<div class="control-group">
+			<label class="control-label">appId</label>
+			<div class="controls">
+				<input name="appId" value="${dataSync.appId }" class="required" <c:if test="${dataSync.cloudExamId!=null }">readonly</c:if>/>
+			</div>
+		</div>
+		<div class="control-group">
+			<label class="control-label">密钥</label>
+			<div class="controls">
+				<input name="secretKey" value="${dataSync.secretKey }" class="required" <c:if test="${dataSync.cloudExamId!=null }">readonly</c:if>/>
+			</div>
+		</div>
+		<div class="control-group">
+			<label class="control-label">云平台考试ID</label>
+			<div class="controls">
+				<input name="cloudExamId" value="${dataSync.cloudExamId }" class="required" <c:if test="${dataSync.cloudExamId!=null }">readonly</c:if>/>
+			</div>
+		</div>
+		<div class="control-group">
+			<label class="control-label">考生接口地址</label>
+			<div class="controls">
+				<textarea name="studentUrl" class="input-xxlarge required" <c:if test="${dataSync.cloudExamId!=null }">readonly</c:if>>${dataSync.studentUrl }</textarea>
+			</div>
+		</div>
+		<div class="control-group">
+			<label class="control-label">试卷结构接口地址</label>
+			<div class="controls">
+				<textarea name="subjectUrl" class="input-xxlarge required" <c:if test="${dataSync.cloudExamId!=null }">readonly</c:if>>${dataSync.subjectUrl }</textarea>
+			</div>
+		</div>
+		<c:if test="${dataSync.cloudExamId!=null }">
+		<div class="control-group">
+			<label class="control-label">试卷结构</label>
+			<div class="controls">
+				<a href="${ctx}/admin/exam/data/sync/export">下载</a>
+			</div>
+		</div>
+		</c:if>
+
+		<div class="form-actions">
+			<c:if test="${!dataSync.finished || running }">
+			<input id="btnSubmit" class="btn btn-primary" type="submit" value="开始同步"/>
+			</c:if>
+			
+			&nbsp;
+			<a href="${ctx}/admin/exam-list" class="btn"/>返回</a>
+		</div>
+	</form:form>
+</body>
+</html>

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

@@ -42,6 +42,14 @@
 				<form:input path="name" htmlEscape="false" maxlength="200" class="required"/>
 			</div>
 		</div>
+		<div class="control-group">
+			<label class="control-label">类型</label>
+			<div class="controls">
+				<select class="input-small" name="type" disabled="disabled" id="examType">
+                	 <option value="${exam.type.value}">${exam.type.name}</option>
+            	</select>
+			</div>
+		</div>
 		<div class="control-group">
 			<label class="control-label">考试日期</label>
 			<div class="controls">
@@ -50,12 +58,14 @@
 					onclick="WdatePicker({dateFmt:'yyyy-MM-dd',isShowClear:true});"/>
 			</div>
 		</div>
+		<c:if test="${exam.type!='MULTI_MEDIA'}">
 		<div class="control-group">
 			<label class="control-label">强制标记</label>
 			<div class="controls">
 				<input name="forceSpecialTag" type="checkbox" <c:if test="${exam.forceSpecialTag}">checked</c:if>/>
 			</div>
 		</div>
+		</c:if>
 		<div class="control-group">
 			<label class="control-label">及格分</label>
 			<div class="controls">
@@ -79,6 +89,7 @@
             </select>
 			</div>
 		</div>
+		<c:if test="${exam.type!='MULTI_MEDIA'}">
 		<div class="control-group">
             <label class="control-label">原图遮盖</label>
             <div class="controls">
@@ -86,6 +97,7 @@
                 <a href="${ctx}/admin/exam/getSheetConfig?examId=${exam.id}" target="_blank" class="required" id= "configuration">设置</a>
             </div>
         </div>
+        </c:if>
 		</c:if>
 		<div class="control-group">
 			<label class="control-label">描述</label>

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

@@ -23,6 +23,13 @@
 					}
 				}
 			});
+			$("#examType").change(function() { 
+				if ($("#examType").val()=='2'){
+					$("#forceSpecialTagDiv").hide();
+				}else{
+					$("#forceSpecialTagDiv").show();
+				}
+			});
 		});
 	</script>
 </head>
@@ -39,6 +46,16 @@
 				<form:input path="name" htmlEscape="false" maxlength="200" class="required"/>
 			</div>
 		</div>
+		<div class="control-group">
+			<label class="control-label">类型</label>
+			<div class="controls">
+				<select class="input-small" name="type" id="examType">
+                <c:forEach items="${typeList}" var="item">
+                	 <option value="${item.value}" <c:if test="${item.value==exam.type.value}">selected</c:if>>${item.name}</option>
+                </c:forEach>
+            </select>
+			</div>
+		</div>
 		<div class="control-group">
 			<label class="control-label">考试日期</label>
 			<div class="controls">
@@ -47,12 +64,14 @@
 					onclick="WdatePicker({dateFmt:'yyyy-MM-dd',isShowClear:true});"/>
 			</div>
 		</div>
+		<div id="forceSpecialTagDiv">
 		<div class="control-group">
 			<label class="control-label">强制标记</label>
 			<div class="controls">
 				<input name="forceSpecialTag" type="checkbox" <c:if test="${exam.forceSpecialTag}">checked</c:if>/>
 			</div>
 		</div>
+		</div>
 		<div class="control-group">
 			<label class="control-label">及格分</label>
 			<div class="controls">

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

@@ -96,6 +96,7 @@
 											<li><a href="${ctx}/admin/exam/mark" target="mainFrame" ><i class="icon-pencil"></i>评卷管理</a></li>
 											<li><a href="${ctx}/admin/exam/score" target="mainFrame" ><i class="icon-search"></i>成绩查询</a></li>
 											<li><a href="${ctx}/admin/exam/reportSubjectRange" target="mainFrame" ><i class="icon-search"></i>成绩分析</a></li>
+											<li><a href="${ctx}/admin/exam/problem/history" target="mainFrame" ><i class="icon-tag"></i>问题试卷</a></li>	
 											<li><a href="${ctx}/admin/exam/check/answer" target="mainFrame" ><i class="icon-check"></i>数据检查</a></li>			
 											</c:if>
 											
@@ -104,6 +105,7 @@
                                             <li><a href="${ctx}/admin/exam/student" target="mainFrame" ><i class="icon-user"></i>考生管理</a></li>
                                             <li><a href="${ctx}/admin/exam/mark" target="mainFrame" ><i class="icon-pencil"></i>评卷管理</a></li>
                                             <li><a href="${ctx}/admin/exam/score" target="mainFrame" ><i class="icon-search"></i>成绩查询</a></li> 
+                                            <li><a href="${ctx}/admin/exam/problem/history" target="mainFrame" ><i class="icon-tag"></i>问题试卷</a></li>	
                                             <li><a href="${ctx}/admin/exam/reportSubjectRange" target="mainFrame" ><i class="icon-search"></i>成绩分析</a></li>     
                                             </c:if>
                                             

+ 5 - 2
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/examList.jsp

@@ -55,8 +55,11 @@
     				<a href="${ctx}/admin/exam-view/${exam.id}">详情</a>
     				<c:if test="${web_user.schoolAdmin==true}">
     				&nbsp;
-    				<a href="${ctx}/admin/exam-edit/${exam.id}">编辑</a>
-    				<a href="${ctx}/admin/exam/problem/type?examId=${exam.id}">问题卷分类</a>
+    					<a href="${ctx}/admin/exam-edit/${exam.id}">编辑</a>
+    					<a href="${ctx}/admin/exam/problem/type?examId=${exam.id}">问题卷分类</a>
+    					<c:if test="${exam.type=='MULTI_MEDIA'}">
+    					<a href="${ctx}/admin/exam/data/sync?examId=${exam.id}">数据同步</a>
+    					</c:if>
     				</c:if>
 				</td>
 			</tr>

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

@@ -85,13 +85,15 @@
             </select>
             </div>
         </div>
-        <div class="control-group">
+        <c:if test="${examType!='MULTI_MEDIA'}">
+		<div class="control-group">
             <label class="control-label">图片显示</label>
             <div class="controls">
-                <form:input path="picList" class="required" id="picList"  type="hidden"/>
-                <a href="${ctx}/admin/exam/group/getPictureConfig?subjectCode=${group.subjectCode}&number=${group.number}" target="_blank" class="required" id= "configuration">设置</a>
+               <a href="${ctx}/admin/exam/group/getPictureConfig?subjectCode=${group.subjectCode}&number=${group.number}" target="_blank" class="required" id= "configuration">设置</a>
             </div>
-        </div>
+        </div> 
+        </c:if>
+        <form:input path="picList" class="required" id="picList"  type="hidden"/>
 		<div class="control-group">
             <label class="control-label">双评</label>
             <div class="controls">
@@ -132,12 +134,14 @@
             </div>
         </div>
         </div>
+        <c:if test="${examType!='MULTI_MEDIA'}">
 		<div class="control-group">
 			<label class="control-label">原卷显示</label>
 			<div class="controls">
 				<input name="sheetView" type="checkbox" <c:if test="${group.sheetView}">checked</c:if>/>
 			</div>
 		</div>
+		</c:if>
         <div class="form-actions">
             <a id="btnSubmit" href="##" class="btn btn-primary">保 存</a>&nbsp;
             <a href="${ctx}/admin/exam/group?subjectCode=${group.subjectCode}" class="btn">返回</a>

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

@@ -108,13 +108,15 @@
             </select>
             </div>
         </div>
-        <div class="control-group">
+        <c:if test="${examType!='MULTI_MEDIA'}">
+    	<div class="control-group">
             <label class="control-label">图片显示</label>
-            <div class="controls">                
-            	<form:input path="picList" class="required" id="picList"  type="hidden"/>
-                <a href="${ctx}/admin/exam/group/getPictureConfig?subjectCode=${group.subjectCode}&number=${group.number}" target="_blank" class="required" id= "configuration">设置</a>
+            <div class="controls"> 
+				<a href="${ctx}/admin/exam/group/getPictureConfig?subjectCode=${group.subjectCode}&number=${group.number}" target="_blank" class="required" id= "configuration">设置</a>
             </div>
-        </div>
+        </div> 
+        </c:if>
+        <form:input path="picList" class="required" id="picList"  type="hidden"/>
 		<div class="control-group">
             <label class="control-label">双评</label>
             <div class="controls">
@@ -155,12 +157,14 @@
             </div>
         </div>
         </div>
+        <c:if test="${examType!='MULTI_MEDIA'}">
  		<div class="control-group">
 			<label class="control-label">原卷显示</label>
 			<div class="controls">
 				<input name="sheetView" type="checkbox" <c:if test="${group.sheetView}">checked</c:if>/>
 			</div>
 		</div>
+		</c:if>
         <div class="control-group">
             <label class="control-label">重要提示</label>
             <div class="controls">

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

@@ -89,13 +89,15 @@
             </select>
             </div>
         </div>
+        <c:if test="${examType!='MULTI_MEDIA'}">
         <div class="control-group">
             <label class="control-label">图片显示</label>
             <div class="controls">
-                <form:input path="picList" class="required" id="picList"  type="hidden"/>
-                <a href="${ctx}/admin/exam/group/getPictureConfig?subjectCode=${group.subjectCode}&number=${group.number}" target="_blank" class="required" id= "configuration">设置</a>
+				<a href="${ctx}/admin/exam/group/getPictureConfig?subjectCode=${group.subjectCode}&number=${group.number}" target="_blank" class="required" id= "configuration">设置</a>
             </div>
         </div>
+        </c:if> 
+		<form:input path="picList" class="required" id="picList"  type="hidden"/>
         <c:forEach items="${questions}" var="question">
         <div class="control-group">
             <label class="control-label">小题${question.subNumber}间隔分</label>
@@ -124,12 +126,14 @@
 <!--             </div> -->
 <!--         </div> -->
 <!--         </div> -->
+		<c:if test="${examType!='MULTI_MEDIA'}">
 		<div class="control-group">
 			<label class="control-label">原卷显示</label>
 			<div class="controls">
 				<input name="sheetView" type="checkbox" <c:if test="${group.sheetView}">checked</c:if>/>
 			</div>
 		</div>
+		</c:if>
         <div class="form-actions">
             <a id="btnSubmit" href="##" class="btn btn-primary">保 存</a>&nbsp;
             <a href="${ctx}/admin/exam/group?subjectCode=${group.subjectCode}" class="btn">返回</a>

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

@@ -28,8 +28,10 @@
 			<input id="btnSubmit" class="btn btn-primary" type="submit" value="查询"/>
 			
 			<c:if test="${web_user.schoolAdmin==true}">
+				<c:if test="${examType!='MULTI_MEDIA'}">
 			&nbsp;
-			<a href="${ctx}/admin/exam/group/add?subjectCode=${subject.code}" class="btn">新增</a>
+				<a href="${ctx}/admin/exam/group/add?subjectCode=${subject.code}" class="btn">新增</a>
+				</c:if>
 			&nbsp;
 			<a href="${ctx}/admin/exam/group/check-count?subjectCode=${subject.code}" class="btn">数量校对</a>
 			</c:if>
@@ -86,8 +88,10 @@
 					<a href="${ctx}/admin/exam/group/reset?subjectCode=${result.subjectCode}&number=${result.number}" data-number="${result.number}" class="reset-button">重置</a>
 					</c:if>
 					&nbsp;
+					<c:if test="${examType!='MULTI_MEDIA'}">
 					<a href="${ctx}/admin/exam/group/edit-simple?subjectCode=${result.subjectCode}&number=${result.number}" data-number="${result.number}" class="edit-button">修改</a>
 					&nbsp;
+					</c:if>
 					<a href="${ctx}/admin/exam/group/delete?subjectCode=${result.subjectCode}&number=${result.number}" data-number="${result.number}" class="delete-button">删除</a>
 					</c:if>
 					

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

@@ -0,0 +1,122 @@
+<%@ page language="java" pageEncoding="utf-8"%>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>云阅卷</title>
+<meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"/>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+
+<link rel="stylesheet" type="text/css" href="${ctxStatic}/rich-text/css/rich-text.css">
+<script type="text/javascript" src="${ctxStatic}/rich-text/js/render.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/viewer/viewer.js"></script>
+<link href="${ctxStatic}/viewer/viewer.css" rel="stylesheet">
+
+</head>
+<body id="index">
+	<div id="json-view-content" class="rich-text">
+	</div>
+</body>
+<script type="text/javascript">
+var url = "${jsonServer}${answerUrl}";
+var questionNumbers ="${questionNumbers}";
+
+$(document).ready(function() {
+	initJsonPopover(url,questionNumbers); 
+});
+
+function initJsonPopover(url,questionNumbers){
+	questionNumbers = questionNumbers.split(",");
+	let questionArr = {};
+	questionNumbers.forEach(questionNumber => {
+		questionArr[questionNumber] = true;
+    });
+	$('#json-view-content').empty();
+    $.ajax({  
+        type:"GET",  
+        url:url,  
+        dataType:"json",  
+        success:function(data){ 
+        	//var data ='[{"mainNumber": 6,"subNumber": 1,"body": {"sections": [{ "blocks":[{"type": "text","value": "我是题目我是题目我是题目"}]}]},"answer": {"sections": [{"blocks":[{"type": "audio","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/19/3_8_19_15748452552644264.mp3"}]}]},"studentAnswer": {"sections": [{"blocks":[{"type": "image","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/20/3_8_20_15748452889591137.jpeg"},{"type": "image","value": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582709869784&di=39683b1330bd09bebd93c95fe925ca70&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn10108%2F170%2Fw600h370%2F20190222%2Fd6de-htknpmh2595255.jpg"}]}]}}]';
+        	//data = JSON.parse(data);
+         	let questions = data || [];
+         	questions.forEach(question => {
+                 // 按题号过滤
+                 if (questionArr[question.mainNumber + "." + question.subNumber] === true || questionNumbers=="") {
+                     let numberTitle = {
+                         "blocks": [{
+                             "type": "text",
+                             "param": {"bold": true},
+                             "value": "题号:" + question.mainNumber + "-" + question.subNumber
+                         }]
+                     };
+                     let bodyTitle = {
+                         "blocks": [{
+                             "type": "text",
+                             "param": {"bold": true},
+                             "value": "题干:"
+                         }]
+                     };
+                     let answerTitle = {
+                         "blocks": [{
+                             "type": "text",
+                             "param": {"bold": true},
+                             "value": "考生答案:"
+                         }, {
+                             "type": "text",
+                             "param": {
+                                 "italic": true,
+                                 "danger": true
+                             },
+                             "value": "(字数统计:" + textCount(question.studentAnswer) + ")"
+                         }]
+                     };
+                     let standardTitle = {
+                         "blocks": [{
+                             "type": "text",
+                             "param": {"bold": true},
+                             "value": "标准答案:"
+                         }]
+                     };
+                     this.holder = $('#json-view-content');
+                     $(renderSection(numberTitle)).appendTo(this.holder);
+                     $(renderSection(bodyTitle)).appendTo(this.holder);
+                     if(question.parentBody!=null && question.parentBody!=undefined){
+     					$(renderRichText(question.parentBody)).appendTo(this.holder); 
+     				}
+                     $(renderRichText(question.body)).appendTo(this.holder);
+                     $(renderSection(answerTitle)).appendTo(this.holder);
+                     $(renderRichText(question.studentAnswer)).appendTo(this.holder);
+                     $(renderSection(standardTitle)).appendTo(this.holder);
+                     $(renderRichText(question.answer)).appendTo(this.holder);
+                 }
+             });
+             if (this.viewer != undefined) {
+                 this.viewer.destroy();
+             }
+         	const viewer = new Viewer($('#json-view-content')[0], {}); 
+          } 
+     }); 
+
+}
+function textCount(jsonBody) {
+    let sections = jsonBody.sections || [];
+    let count = 0;
+    sections.forEach(section => {
+        let blocks = section.blocks || [];
+        blocks.forEach(block => {
+            if (block.type === 'text' && block.value != undefined) {
+                count += block.value.length;
+            }
+        });
+    });
+    return count;
+}
+</script>
+
+
+

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

@@ -60,7 +60,9 @@
 			&nbsp;&nbsp;
 			<input id="btnSubmit" class="btn btn-primary" type="button" value="查询" onclick="goSearch()"/>
 			&nbsp;
+			<c:if test="${examType!='MULTI_MEDIA'}">
 			<a target="_blank" href="${ctx}/admin/exam/inspected/start?subjectCode=${query.subjectCode}&groupNumber=${query.groupNumber}" class="btn">待复核:${inspectedCount }</a>
+			</c:if>
 		</div>
 	</form>
 	<tags:message content="${message}"/>
@@ -121,7 +123,12 @@
 				</td>
 				<td>
 				    <c:if test="${result.status.value==1 || result.status.value==3 ||result.status.value==5 ||result.status.value==6}">
-				    <a class="track-link" href="##" data-image-url="${ctx}/admin/exam/track/byLibrary?libraryId=${result.id}" data-title="${result.examNumber}">阅卷轨迹</a>
+				   		<c:if test="${examType=='MULTI_MEDIA'}">
+				    		<a class="json-link" href="${ctx}/admin/exam/library/getJson?studentId=${result.studentId}&groupNumber=${result.groupNumber}" target="_blank">原图</a> 
+				    	</c:if>
+				    	<c:if test="${examType!='MULTI_MEDIA'}">
+	 				    	<a class="track-link" href="#" data-image-url="${ctx}/admin/exam/track/byLibrary?libraryId=${result.id}" data-title="${result.examNumber}">阅卷轨迹</a>
+				    	</c:if>
 				    </c:if>
 				    <c:if test="${result.status.value==1 || result.status.value==5 ||result.status.value==6}">
 				    &nbsp;
@@ -226,4 +233,4 @@ function goSearch(){
 }
 </script>	
 </body>
-</html>
+</html>

+ 6 - 2
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/markerList.jsp

@@ -370,6 +370,10 @@
                 wrongMessage.html('请输入正整数!');
                 return false;
             }
+            if(taskCount>2147483647 ){
+ 		       wrongMessage.html('数量不合法!');
+ 		       return false;
+ 		     }
         }
         $.post('${ctx}/admin/exam/marker/setTaskCount', {id: markerId, taskCount: taskCount}, function (result) {
             if (result.success == true) {
@@ -400,7 +404,7 @@
             $('.reSetPasswordWin').hide();
         });
     });
-
 </script>
+
 </body>
-</html>
+</html>

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

@@ -125,7 +125,11 @@
 				<td>${subject.objectiveScore}</td>
 				<td>${subject.subjectiveScore}</td>
 				<td>${subject.totalScore}</td>
-				<td><a href="${ctx}/admin/exam/subject/edit?code=${subject.code}">原图遮盖</a></td>
+				<td>
+				<c:if test="${examType!='MULTI_MEDIA'}">
+				<a href="${ctx}/admin/exam/subject/edit?code=${subject.code}">原图遮盖</a>
+				</c:if>
+				</td>
 			</tr>
 		</c:forEach>
 		</tbody>

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

@@ -0,0 +1,12 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<html>
+<head>
+	<title>云阅卷</title>
+	<meta name="decorator" content="default"/>
+	<%@include file="/WEB-INF/views/include/head.jsp" %>
+</head>
+<body>
+	多媒体阅卷无此功能权限
+</body>
+</html>

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

@@ -0,0 +1,149 @@
+<%@ page contentType="text/html;charset=UTF-8" %>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<html>
+<head>
+	<title>标记试卷处理</title>
+	<meta name="decorator" content="default"/>
+	<%@include file="/WEB-INF/views/include/head.jsp" %>
+	<style type="text/css">.sort{color:#0663A2;cursor:pointer;}</style>
+</head>
+<body>
+	<form id="searchForm" action="${ctx}/admin/exam/problem/history" method="post" class="breadcrumb form-search">
+		<input type="hidden" id="pageNumber" name="pageNumber" value="${query.pageNumber }"/>
+		<input type="hidden" id="pageSize" name="pageSize" value="${query.pageSize }"/>
+		<div>
+			<label>科目</label>
+			<select class="input-large" name="subjectCode" id="subject-select">
+				<option value="">请选择</option>
+				<c:forEach items="${subjectList}" var="subject">
+				<option value="${subject.code}" <c:if test="${subject.code==query.subjectCode}">selected</c:if>>${subject.code}-${subject.name}</option>
+				</c:forEach>
+			</select>
+			<label>大题</label>
+            <select class="input-medium" id="group-select" name="groupNumber">
+             	<option value="0">请选择</option>
+                <c:forEach items="${groupList}" var="item">
+                <option value="${item.number}" <c:if test="${item.number==query.groupNumber}">selected</c:if>>${item.number}-${item.title}</option>
+                </c:forEach>
+            </select>
+			<label>类型</label>
+			<select class="input-medium" name="problemId">
+				<option value="">请选择</option>
+				<c:forEach items="${problemList}" var="problem">
+				<option value="${problem.id}" <c:if test="${problem.id==query.problemId}">selected</c:if>>${problem.name}</option>
+				</c:forEach>
+			</select>
+			&nbsp;
+			<label>考生编号</label>
+            <input type="text" name="studentId" id="studentId" value="${query.studentId}" maxlength="10" class="input-small"onkeyup="this.value=this.value.replace(/\D/g,'')"/>
+            &nbsp;
+			<input id="btnSubmit" class="btn btn-primary" type="button" value="查询" onclick="goSearch()"/>
+		</div>
+	</form>
+	<tags:message content="${message}"/>
+	<table id="contentTable" class="table table-striped table-bordered table-condensed">
+		<thead>
+			<tr>
+				<th>科目</th>
+				<th>分组序号</th>
+				<th>准考证号</th>
+				<th>考生编号</th>
+				<th>姓名</th>
+				<th>提交时间</th>
+				<th>问题类型</th>
+				<th>操作</th>
+			</tr>
+		</thead>
+		<tbody>
+		<c:forEach items="${resultList}" var="result">
+			<tr>
+				<td>${result.subjectCode}-${result.subjectName}</td>
+				<td>${result.number}</td>
+				<td>${result.examNumber}</td>
+				<td>${result.id}</td>
+				<td>${result.name}</td>
+				<td>${result.markTime}</td>
+				<td>${result.problemType.name}</td>
+				<td>
+				   		<c:if test="${examType=='MULTI_MEDIA'}">
+				    		<a class="json-link" href="${ctx}/admin/exam/library/getJson?studentId=${result.id}&groupNumber=${result.number}" target="_blank">原图</a> 
+				    	</c:if>
+				    	<c:if test="${examType!='MULTI_MEDIA'}">
+	 				    	<a class="track-link" href="#" data-image-url="${ctx}/admin/exam/track/byLibrary?libraryId=${result.libraryId}" data-title="${result.examNumber}">阅卷轨迹</a>
+				    	</c:if>
+				    &nbsp;
+                    <a href="##" data-id="${result.libraryId}" class="back-link">打回</a>
+				</td>
+			</tr>
+		</c:forEach>
+		</tbody>
+	</table>
+	<div class="pagination">${query}</div>
+	<%@include file="/WEB-INF/views/include/trackView.jsp" %>
+<script type="text/javascript">
+$(document).ready(function() {
+    /* new jBox('Image', {
+    	imageFade: 0,
+    	delayOpen: 0,
+    	delayClose: 0,
+    	maxHeight: $(window).height()*0.88
+    }); */
+    $('.back-link').click(function(){
+    	if(!confirm('确定要打回该评卷任务吗?')){
+    	    return;
+    	}
+    	$.post('${ctx}/admin/exam/library/back', {id: $(this).attr('data-id')}, function(result){
+    	    if(result.success==true){
+                alert('打回成功');
+                $("#searchForm").submit();
+            }else{
+                alert(result.message);
+            }
+    	});
+    });
+    $('.track-link').click(function(){
+        initTrackPopover($(this).attr('data-title'),$(this).attr('data-image-url'));
+        return false;
+    });
+    $('#subject-select').change(function(){
+        var code = $(this).val();
+        $('#group-select').empty();
+        $("<option value='0'>请选择</option>").appendTo('#group-select');
+        if(code==''){
+            $('#group-select').val('').trigger('change');
+            return;
+        }
+        
+        $.post('${ctx}/admin/exam/group/query', {subjectCode: code, status: 'FORMAL'}, function(result){
+            var parent = $('#group-select');
+            var first = '';
+            for(var i=0;i<result.length;i++){
+                var group = result[i];
+                $('<option value="'+group.number+'">'+group.number+'-'+group.title+'</option>').appendTo(parent);
+                if(i==0){
+                    first = group.number;
+                }
+            }
+            parent.val(first).trigger('change');
+        });
+    });
+});
+function page(n,s){
+	$("#pageNumber").val(n);
+	$("#searchForm").attr('action', '${ctx}/admin/exam/problem/history');
+	$("#searchForm").submit();
+	return false;
+}
+function goSearch(){
+    var studentId = parseInt($("#studentId").val());
+    if( $("#studentId").val()!="" && studentId>2147483647 ){
+    	alert("考生编号不合法");return false;
+    };
+	$("#pageNumber").val(1);
+	$("#searchForm").attr('action', '${ctx}/admin/exam/problem/history');
+	$("#searchForm").submit();
+	return false;
+}
+</script>	
+</body>
+</html>

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

@@ -19,6 +19,9 @@
 <script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
 <link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
 
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/mark-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/sheet-view.js"></script>
@@ -29,6 +32,7 @@
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/single-image-view.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/view-sidebar.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/quality-process.js"></script>
+
 </head>
 <body>
 	<div class="container-fluid" id="container"></div>

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

@@ -0,0 +1,74 @@
+<%@ page language="java" pageEncoding="utf-8"%>
+<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>云阅卷高校版</title>
+<link href="${ctxStatic}/mark-new/css/bootstrap.css" rel="stylesheet" type="text/css" />
+<link href="${ctxStatic}/mark-new/css/style.css" rel="stylesheet" type="text/css" />
+
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/json2.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+
+<script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
+<link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css" rel="stylesheet" />
+
+<script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
+<link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
+
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
+<script src="${ctxStatic}/rich-text/js/render.js"></script>
+<link href="${ctxStatic}/rich-text/css/rich-text.css" rel="stylesheet">
+<script type="text/javascript" src="${ctxStatic}/viewer/viewer.js"></script>
+<link href="${ctxStatic}/viewer/viewer.css" rel="stylesheet">
+
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/mark-control.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/json-view.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-json/js/json-loader.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/header-mark-status.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/image-builder.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/warning-info.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/thumbnail.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/single-image-view.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/view-sidebar.js"></script>
+<script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/quality-process.js"></script>
+
+</head>
+<body>
+	<div class="container-fluid" id="container"></div>
+	<script type="text/javascript">
+		$(document).ready(function() {
+			var mc = new MarkControl({
+				container : $('#container'),
+				staticServer : '${ctxStatic}',
+				imageServer : '${sliceServer}',
+				userName : '${web_user.name}',
+				logoutTitle: '关闭',
+				logoutUrl: 'javascript:window.close()',
+				modules : {
+					'json-loader': {
+	                    jsonServer: '${jsonServer}'
+	                },
+	                'json-view': {},
+					'header-mark-status': {
+						title : '${subject.code}_${subject.name}_${group.title}'
+					},
+					'quality-process': {
+					},
+					'warning-info': {
+					}
+				}
+			});
+            $.post('${ctx}/admin/exam/quality/history?markerId=${markerId}&markerScore=${markerScore}&pageSize=1', {}, function(taskList){
+                mc.setTask(taskList[0]);
+            });
+		});
+	</script>
+</body>
+</html>

+ 10 - 2
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/scoreList.jsp

@@ -95,7 +95,10 @@
 		<c:forEach items="${query.result}" var="student">
 			<tr>
 				<td>
+				<c:if test="${examType=='MULTI_MEDIA'}">${student.examNumber}</c:if>
+				<c:if test="${examType!='MULTI_MEDIA'}">
 				<a href="##" class="detail-link" data-exam-number="${student.examNumber}">${student.examNumber}</a>
+				</c:if>
 				</td>
 				<td>${student.name}</td>
 				<td>${student.studentCode}</td>
@@ -164,8 +167,13 @@
                 </td>
 				<td>
 				    <c:if test="${student.upload==true}">
-					<a class="sheet-link" href="##" data-id="${student.id}" data-sheet-url="${student.sheetUrlString}" data-answer-url="<c:if test="${student.answerUrl!=null}">${cardServer}${student.answerUrl}</c:if>" data-title="${student.examNumber}&nbsp;&nbsp;${student.name}&nbsp;&nbsp;客观总分${student.objectiveScoreString}&nbsp;&nbsp;主观总分${student.subjectiveScoreString}&nbsp;&nbsp;全卷总分${student.totalScoreString}">原图</a>
-					<a href="${ctx}/admin/exam/track/student/${student.id}" target="_blank">轨迹图</a>
+				    	<c:if test="${examType!='MULTI_MEDIA'}">
+	 					<a class="sheet-link" href="##" data-id="${student.id}" data-sheet-url="${student.sheetUrlString}" data-answer-url="<c:if test="${student.answerUrl!=null}">${cardServer}${student.answerUrl}</c:if>" data-title="${student.examNumber}&nbsp;&nbsp;${student.name}&nbsp;&nbsp;客观总分${student.objectiveScoreString}&nbsp;&nbsp;主观总分${student.subjectiveScoreString}&nbsp;&nbsp;全卷总分${student.totalScoreString}">原图</a>
+ 						<a href="${ctx}/admin/exam/track/student/${student.id}" target="_blank">轨迹图</a>
+						</c:if>
+						<c:if test="${examType=='MULTI_MEDIA'}">
+						<a class="json-link" href="${ctx}/admin/exam/library/getJson?studentId=${student.id}" target="_blank">原图</a> 
+						</c:if>
 					</c:if>
 					<c:if test="${student.packageUrlString!=null && student.packageUrlString!=''}">
 					<a class="package-link" href="##" data-image-url="${student.packageUrlString}" data-title="${student.packageCode}">签到表</a>

+ 25 - 3
stmms-web/src/main/webapp/WEB-INF/views/modules/exam/studentList.jsp

@@ -16,7 +16,7 @@
 			<a href="${ctx}/admin/exam/student/template">下载模板</a>
 		</form>
 	</div>
-	    <div id="breachImportBox" class="hide">
+	<div id="breachImportBox" class="hide">
         <form id="breachImportForm" action="${ctx}/admin/exam/student/breachImport" method="post" enctype="multipart/form-data"
               style="padding-left:20px;text-align:center;" class="form-search" onsubmit="loading('正在导入,请稍等...');"><br/>
             <input id="breachUploadFile" name="file" type="file" style="width:330px"/><br/><br/>  
@@ -31,6 +31,14 @@
             <input id="absentBtnImportSubmit" class="btn btn-primary" type="submit" value="缺考考生导入"/>
             <a href="${ctx}/admin/exam/student/absentTemplate">下载模板</a>
         </form>
+    </div>
+    <div id="uploadImportBox" class="hide">
+        <form id="uploadImportForm" action="${ctx}/admin/exam/student/uploadImport" method="post" enctype="multipart/form-data"
+              style="padding-left:20px;text-align:center;" class="form-search" onsubmit="loading('正在导入,请稍等...');"><br/>
+            <input id="studentUploadFile" name="file" type="file" style="width:330px"/><br/><br/>  
+            <input id="uploadBtnImportSubmit" class="btn btn-primary" type="submit" value="多媒体考生上传导入"/>
+            <a href="${ctx}/admin/exam/student/uploadTemplate">下载模板</a>
+        </form>
     </div>
 	<form id="searchForm"  action="${ctx}/admin/exam-param/student" method="post" class="breadcrumb form-search">
 		<input type="hidden" id="pageNumber" name="pageNumber" value="${query.pageNumber }"/>
@@ -96,6 +104,7 @@
 			<c:if test="${web_user.schoolAdmin==true}">
 			&nbsp;<a href="${ctx}/admin/exam/student/add" class="btn btn-primary">添加</a>
 			&nbsp;<input id="btnImport" class="btn" type="button" value="导入"/>
+				<c:if test="${examType!='MULTI_MEDIA' }">
 			&nbsp;<div class="btn-group">
                         <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
                             导入名单<span class="caret"></span>
@@ -103,8 +112,10 @@
                         <ul class="dropdown-menu">
                             <li><a href="##" id="breachBtnImport">违纪名单</a></li>
                             <li><a href="##" id="absentBtnImport">缺考名单</a></li>
+<!--                             <li><a href="##" id="uploadBtnImport">多媒体上传名单</a></li> -->
                         </ul>
-            </div>
+            	</div>
+            	</c:if>
 			</c:if>
 			<c:if test="${query.totalCount>0 && query.totalCount<=10000}">
 			&nbsp;<input id="export-button" class="btn" type="button" value="导出"/>
@@ -142,7 +153,12 @@
 				<td>${student.subject.category}</td>
 				<td>
 				<c:if test="${student.upload==true}">
-				<a class="sheet-link" href="##" data-sheet-url="${student.sheetUrlString}" data-answer-url="<c:if test="${student.answerUrl!=null}">${cardServer}${student.answerUrl}</c:if>" data-title="${student.examNumber}&nbsp;&nbsp;${student.name}&nbsp;&nbsp;客观总分${student.objectiveScoreString}&nbsp;&nbsp;主观总分${student.subjectiveScoreString}&nbsp;&nbsp;全卷总分${student.totalScoreString}">已上传</a>
+					<c:if test="${examType!='MULTI_MEDIA'}">
+					<a class="sheet-link" href="##" data-sheet-url="${student.sheetUrlString}" data-answer-url="<c:if test="${student.answerUrl!=null}">${cardServer}${student.answerUrl}</c:if>" data-title="${student.examNumber}&nbsp;&nbsp;${student.name}&nbsp;&nbsp;客观总分${student.objectiveScoreString}&nbsp;&nbsp;主观总分${student.subjectiveScoreString}&nbsp;&nbsp;全卷总分${student.totalScoreString}">已上传</a>
+					</c:if>
+					<c:if test="${examType=='MULTI_MEDIA'}">
+					已上传
+					</c:if>
 				&nbsp;
 				<c:if test="${student.absent==true}">
 				缺考
@@ -187,9 +203,11 @@
 				<td>
 				    <c:if test="${web_user.schoolAdmin==true}">
     				<a href="${ctx}/admin/exam/student/update?id=${student.id}">修改</a>
+    				<c:if test="${examType!='MULTI_MEDIA'}">
 					&nbsp;
     				<a href="${ctx}/admin/exam/student/delete?id=${student.id}" onclick="return confirmx('确认要删除该考生吗?', this.href)">删除</a>
 					</c:if>
+					</c:if>
 				</td>
 			</tr>
 		</c:forEach>
@@ -228,6 +246,10 @@ $("#absentBtnImport").click(function(){
 	$.jBox($("#absentImportBox").html(), {title:"导入数据", buttons:{"关闭":true},
 		bottomText:"导入文件不能超过5M,仅允许导入“xls”或“xlsx”格式文件!"});
 });
+$("#uploadBtnImport").click(function(){
+	$.jBox($("#uploadImportBox").html(), {title:"导入数据", buttons:{"关闭":true},
+		bottomText:"导入文件不能超过5M,仅允许导入“xls”或“xlsx”格式文件!"});
+});
 $("#export-button").click(function(){
 	$("#searchForm").attr("action","${ctx}/admin/exam/student/export");
 	$("#searchForm").submit();

+ 104 - 0
stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markJson.jsp

@@ -0,0 +1,104 @@
+<%@ page language="java" pageEncoding="utf-8" %>
+<%@ include file="/WEB-INF/views/include/taglib.jsp" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+    <title>云阅卷高校版</title>
+    <link href="${ctxStatic}/mark-new/css/bootstrap.css" rel="stylesheet" type="text/css"/>
+    <link href="${ctxStatic}/mark-new/css/style.css" rel="stylesheet" type="text/css"/>
+
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/json2.js"></script>
+
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.min.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery-ui.min.js "></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/jquery.mousewheel.min.js"></script>
+
+    <script type="text/javascript" src="${ctxStatic}/iviewer/jquery.iviewer.js"></script>
+    <link rel="stylesheet" href="${ctxStatic}/iviewer/jquery.iviewer.css" rel="stylesheet"/>
+
+    <script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
+    <link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
+    
+    <script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+	<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
+    <script src="${ctxStatic}/rich-text/js/render.js"></script>
+    <link href="${ctxStatic}/rich-text/css/rich-text.css" rel="stylesheet">
+	<script type="text/javascript" src="${ctxStatic}/viewer/viewer.js"></script>
+    <link href="${ctxStatic}/viewer/viewer.css" rel="stylesheet">
+
+    <script type="text/javascript" src="${ctxStatic}/mark-json/js/mark-control.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-json/js/json-view.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-json/js/json-loader.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/paper-view.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/image-builder.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/mark-board.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/mark-history.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/mark-status.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/warning-info.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/change-name.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/view-sidebar.js"></script>
+    <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/problem-process.js"></script>
+
+</head>
+<body>
+<div class="container-fluid" id="container"></div>
+<script type="text/javascript">
+    $(document).ready(function () {
+        var mc = new MarkControl({
+            container: $('#container'),
+            staticServer: '${ctxStatic}',
+            imageServer: '${sliceServer}',
+            userId: '${marker.id}',
+            userName: '${web_user.name}',
+            logoutUrl: '${ctx}/mark/logout',
+            //clearUrl: '${ctx}/mark/clear',
+            /* 	<c:if test="${forceMode==false}">
+				switchTrackUrl: '${ctx}/mark/index?mode=track',
+				</c:if>
+				forceSpecialTag : eval('${forceSpecialTag}'.toLowerCase()), */
+            <c:if test="${defaultSetting!=null}">
+            defaultSetting: '${defaultSetting}',
+            </c:if>
+            settingSyncUrl: '${ctx}/mark/update-setting',
+            modules: {
+                'json-loader': {
+                    jsonServer: '${jsonServer}'
+                },
+                'json-view': {},
+                'mark-status': {
+                    simple: false,
+                    subjectName: '${subject.displayName}'
+                },
+                'mark-history': {
+                    pageSize: 10
+                },
+                'mark-board': {
+                    showScoreBoard: false,
+                    autoSubmit: false,
+                    needConfirm: false,
+                },
+                'warning-info': {},
+                <c:if test="${isFormal==true}">
+				'problem-process':{
+					problemTypes : '${problemTypes}'
+                },
+				</c:if>	
+                'change-name': {
+                    url: '${ctx}/mark/change-name'
+                }
+            }
+        });
+        mc.start({
+            mode: 'loop',
+            statusUrl: '${ctx}/mark/status',
+            getUrl: '${ctx}/mark/gettask',
+            historyUrl: '${ctx}/mark/gethistory',
+            submitUrl: '${ctx}/mark/savetask'
+            //clearUrl : '${ctx}/mark/clear'
+        });
+    });
+</script>
+</body>
+</html>

+ 4 - 1
stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markNew.jsp

@@ -19,6 +19,9 @@
 <script src="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.js"></script>
 <link href="${ctxStatic}/perfect-scrollbar/min/perfect-scrollbar.min.css" rel="stylesheet">
 
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/mark-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/modules/answer-view.js"></script>
@@ -43,7 +46,7 @@
 	$(document).ajaxError(function(evt, req, settings){
 		if(req.status==401) {
 			alert("权限失效,请重新登录!");
-			 window.location.href = req.statusText;
+			window.location.href = req.statusText;
        	}
 	});
 

+ 3 - 0
stmms-web/src/main/webapp/WEB-INF/views/modules/mark/markTrack.jsp

@@ -13,6 +13,9 @@
 <script type="text/javascript" src="${ctxStatic}/mark-track/js/jquery-ui.min.js "></script>
 <script type="text/javascript" src="${ctxStatic}/mark-track/js/jquery.mousewheel.min.js"></script>
 
+<script src="${ctxStatic}/i18n/jquery.i18n.properties.js" type="text/javascript"></script>
+<script src="${ctxStatic}/i18n/load.js" type="text/javascript"></script>
+
 <script type="text/javascript" src="${ctxStatic}/mark-track/js/mark-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-new/js/task-control.js"></script>
 <script type="text/javascript" src="${ctxStatic}/mark-track/js/modules/object-view.js"></script>

+ 4 - 0
stmms-web/src/main/webapp/WEB-INF/views/modules/mark/picConfig.jsp

@@ -48,6 +48,10 @@
 </body>
 
 <script type="text/javascript">
+var message = '${message}';
+if(message!=null&&message!=''&&message!=undefined){
+	alert(message);
+}
 var image_server = '${imageServer}';
 var pictureConfig = '${pictureConfig}';
 var picUrls = '${picUrls}';

+ 14 - 13
stmms-web/src/main/webapp/WEB-INF/views/modules/mark/reset.jsp

@@ -2,7 +2,7 @@
 <%@ include file="/WEB-INF/views/include/taglib.jsp"%>
 <html>
 <head>
-	<title>评卷员设置</title>
+	<title><spring:message code="user.reset.title"/></title>
 	<meta name="decorator" content="default"/>
 	<%@include file="/WEB-INF/views/include/head.jsp" %>
     <link rel="stylesheet" href="${ctxStatic}/common/login.css">
@@ -17,26 +17,26 @@
 			$('#submit-button').click(function(){
 				var name = $('#loginName').val();
 				if(name==''){
-					showError('请输入姓名');
+					showError($.i18n.prop("user.reset.name"));
 					return false;
 				}
 				if(name.length>10){
-					showError('姓名长度不应超过10个字');
+					showError($.i18n.prop("user.reset.name.length"));
 					return false;
 				}
 
 				var password = $('#password').val();
 				var password2 = $('#password2').val();
 				if(password==''){
-					showError('请输入密码');
+					showError($.i18n.prop("user.reset.password"));
 					return false;
 				}
-				if(password.length>8){
-					showError('密码长度不应超过8个字符');
+				if(password.length>8 || password.length<4){
+					showError($.i18n.prop("user.reset.password.length"));
 					return false;
 				}
 				if(password!=password2){
-					showError('两次密码输入内容不一致');
+					showError($.i18n.prop("user.reset.password.same"));
 					return false;
 				}
 				return true;
@@ -52,6 +52,7 @@
 		if(self.frameElement && self.frameElement.tagName=="IFRAME"){
 			parent.location.reload();
 		}
+		load();
 	</script>
 </head>
 <body>
@@ -63,26 +64,26 @@
 	   </div>
 	   <div class="right">
 	     <div class="title">
-	       <h1>首次登陆,请完善资料</h1>
+	       <h1 data-i18n-text="user.reset.title">首次登录,请完善资料</h1>
 	       <br/>
 	     </div>
 	     <div class="loginbox">
 	      <form action="${ctx}/mark/reset" method="post" role="form" id="loginForm">
 	        <div class="input-group">
-	        	<input type="text" class="form-control required" id="loginName" name="name" placeholder="请输入姓名" autocomplete="off" value="${loginName }"/>
+	        	<input type="text" class="form-control required" id="loginName" name="name" placeholder="请输入姓名" autocomplete="off" value="${loginName }" data-i18n-placeholder="user.reset.name"/>
 	        </div>
 	        <div class="input-group">
-	        	<input type="password" class="form-control required" id="password" name="password" placeholder="请输入密码" autocomplete="off"/>
+	        	<input type="password" class="form-control required" id="password" name="password" placeholder="请输入密码" autocomplete="off" data-i18n-placeholder="user.reset.password"/>
 	        </div>
 	        <div class="input-group">
-	        	<input type="password" class="form-control required" id=password2 name="password2" placeholder="再次输入密码" autocomplete="off"/>
+	        	<input type="password" class="form-control required" id=password2 name="password2" placeholder="再次输入密码" autocomplete="off" data-i18n-placeholder="user.reset.password.again"/>
 	        </div>
 	        <div class="point hide">
 	        <em class="error"></em>
 	        </div>
 	        <div class="login-btn">
-	        	<input class="opacity" type="submit" id="submit-button" value="确 定"/>
-	        	<a href="${ctx}/logout" class="opacity">退 出</a>
+	        	<input class="opacity" type="submit" id="submit-button" value="确 定" data-i18n-value="user.reset.submit"/>
+	        	<a href="${ctx}/logout" class="opacity" data-i18n-text="user.reset.logout">退 出</a>
 	        </div>
 	      </form>
 	     </div>

+ 60 - 52
stmms-web/src/main/webapp/WEB-INF/views/modules/sys/login.jsp

@@ -1,64 +1,72 @@
 <%@ page contentType="text/html;charset=UTF-8" %>
-<%@ include file="/WEB-INF/views/include/taglib.jsp"%>
+<%@ include file="/WEB-INF/views/include/taglib.jsp" %>
 <html>
 <head>
-	<title>登录</title>
-	<meta name="decorator" content="default"/>
-	<%@include file="/WEB-INF/views/include/head.jsp" %>
+    <title><spring:message code="user.login.submit"/></title>
+    <meta name="decorator" content="default"/>
+    <%@include file="/WEB-INF/views/include/head.jsp" %>
     <link rel="stylesheet" href="${ctxStatic}/common/login.css">
-	<script type="text/javascript">
-		$(document).ready(function() {
-			$("#showType").hide();
-			$("#adminLogin").click(function() {
-				$("#adminLogin").addClass("on");
-				$("#markLogin").removeClass("on");
-				$("#showType").attr("value","admin-login");
-			});
-			$("#markLogin").click(function() {
-				$("#markLogin").addClass("on");
-				$("#adminLogin").removeClass("on");
-				$("#showType").attr("value","mark-login");
-			});
-			if(self.frameElement && self.frameElement.tagName=="IFRAME"){
-				parent.location.reload();
-			}
-		});
-	</script>
+    <script type="text/javascript">
+        $(document).ready(function () {
+            $("#showType").hide();
+            $("#adminLogin").click(function () {
+                $("#adminLogin").addClass("on");
+                $("#markLogin").removeClass("on");
+                $("#showType").attr("value", "admin-login");
+            });
+            $("#markLogin").click(function () {
+                $("#markLogin").addClass("on");
+                $("#adminLogin").removeClass("on");
+                $("#showType").attr("value", "mark-login");
+            });
+            if (self.frameElement && self.frameElement.tagName == "IFRAME") {
+                parent.location.reload();
+            }
+            load();
+        });
+    </script>
 </head>
 <body>
 <div class="container">
-  <div class="middle cl">
-   <div class="left">
-    <div class="logo"><img src="${ctxStatic}/images/<c:if test="${indexLogo ==null||indexLogo =='' }">logo@2x.png</c:if><c:if test="${indexLogo !=null }">${indexLogo }</c:if>"/></div>
-    <p>Copyright &#169武汉启明软件 2012-2014</p>
-   </div>
-   <div class="right">
-     <div class="title">
-       <h1>高校考试管理平台</h1>
-       <c:if test="${loginType == 'admin-login' }"><p>管理员登录</p></c:if>
-       <c:if test="${loginType == 'mark-login' }"><p>评卷员登录</p></c:if>
-       <c:if test="${loginType == null || loginType == '' }">
-       <p><span><a href="#" class="<c:if test="${showType == 'admin-login' ||showType==null}">on</c:if>" id="adminLogin">管理员登录</a></span>
-       	<span><a href="#" class="<c:if test="${showType == 'mark-login' }">on</c:if>" id="markLogin">评卷员登录</a></span></p>
-       </c:if>
-     </div>
-     <div class="loginbox">
-      <form action="${ctx}/login" method="post" role="form" id="loginForm">
-      	<input id ="showType" name ="showType" value="${showType}" />
-        <div class="input-group">
-        	<input type="text" class="form-control required" id="loginName" name="loginName" placeholder="请输入用户名" autocomplete="off" value="${loginName }"/>
+    <div class="middle cl">
+        <div class="left">
+            <div class="logo"><img
+                    src="${ctxStatic}/images/<c:if test="${indexLogo ==null||indexLogo =='' }">logo@2x.png</c:if><c:if test="${indexLogo !=null }">${indexLogo }</c:if>"/>
+            </div>
+            <p>Copyright &#169武汉启明软件 2012-2014</p>
         </div>
-        <div class="input-group">
-        	<input type="password" class="form-control required" id="password" name="password" placeholder="请输入密码" autocomplete="off"/>
+        <div class="right">
+            <div class="title">
+                <h1 data-i18n-text="user.login.title">高校考试管理平台</h1>
+                <c:if test="${loginType == 'admin-login' }"><p data-i18n-text="user.login.admin">管理员登录</p></c:if>
+                <c:if test="${loginType == 'mark-login' }"><p data-i18n-text="user.login.marker">评卷员登录</p></c:if>
+                <c:if test="${loginType == null || loginType == '' }">
+                    <p><span><a href="#" class="<c:if test="${showType == 'admin-login' ||showType==null}">on</c:if>"
+                                id="adminLogin" data-i18n-text="user.login.admin">管理员登录</a></span>
+                        <span><a href="#" class="<c:if test="${showType == 'mark-login' }">on</c:if>" id="markLogin" data-i18n-text="user.login.marker">评卷员登录</a></span></p>
+                </c:if>
+            </div>
+            <div class="loginbox">
+                <form action="${ctx}/login" method="post" role="form" id="loginForm">
+                    <input id="showType" name="showType" value="${showType}"/>
+                    <div class="input-group">
+                        <input type="text" class="form-control required" id="loginName" name="loginName" placeholder="请输入用户名"
+                               autocomplete="off" value="${loginName }" data-i18n-placeholder="user.login.name"/>
+                    </div>
+                    <div class="input-group">
+                        <input type="password" class="form-control required" id="password" name="password" placeholder="请输入密码"
+                               autocomplete="off" data-i18n-placeholder="user.login.password"/>
+                    </div>
+                    <c:if test="${message != null }">
+                    <div class="point <c:if test="${message != null }"></c:if><c:if test="${message == null }">hide</c:if>">
+                        <em class="error"><spring:message code="${message }"/></em>
+                    </div>
+                    </c:if>
+                    <div class="login-btn"><input class="opacity" type="submit" value="登 录" data-i18n-value="user.login.submit"/></div>
+                </form>
+            </div>
         </div>
-        <div class="point <c:if test="${message != null }"></c:if><c:if test="${message == null }">hide</c:if>">
-        <em class="error">${message }</em>
-        </div>
-        <div class="login-btn"><input class="opacity" type="submit" value="登 录"/></div>
-      </form>
-     </div>
-   </div>
-  </div>
+    </div>
 </div>
 </body>
 </html>

+ 1 - 1
stmms-web/src/main/webapp/WEB-INF/views/modules/user/userEdit.jsp

@@ -74,7 +74,7 @@
 		<div class="control-group" id="subject-code-div">
             <label class="control-label">绑定科目</label>
             <div class="controls">
-                <form:input path="subjectCode" htmlEscape="false" maxlength="10"/>
+                <form:input path="subjectCode" htmlEscape="false" maxlength="30"/>
             </div>
         </div>
 		<div class="control-group">

+ 64 - 40
stmms-web/src/main/webapp/sql/stmms_ft.sql

@@ -23,7 +23,7 @@ CREATE TABLE `b_campus`
     PRIMARY KEY (`id`),
     KEY `index1` (`school_id`, `name`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='学习中心表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='学习中心表';
 
 
 
@@ -51,7 +51,7 @@ CREATE TABLE `b_school`
     UNIQUE KEY `index1` (`access_key`),
     KEY `area` (`province`, `city`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='学校表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='学校表';
 
 
 # Dump of table b_user
@@ -79,7 +79,7 @@ CREATE TABLE `b_user`
     KEY `index1` (`login_name`),
     KEY `index2` (`school_id`, `enable`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='用户表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='用户表';
 
 LOCK TABLES `b_user` WRITE;
 
@@ -108,7 +108,7 @@ CREATE TABLE `b_open_account`
     PRIMARY KEY (`id`),
     UNIQUE KEY `index1` (`school_id`, `account`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='第三方账号表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='第三方账号表';
 
 
 # Dump of table eb_check_student
@@ -125,7 +125,7 @@ CREATE TABLE `eb_check_student`
     `update_time`  datetime   DEFAULT NULL COMMENT '修改时间',
     PRIMARY KEY (`student_id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='审核学生表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='审核学生表';
 
 
 # Dump of table eb_exam
@@ -155,7 +155,7 @@ CREATE TABLE `eb_exam`
     PRIMARY KEY (`id`),
     KEY `index1` (`school_id`, `create_time`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='考试表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='考试表';
 
 
 
@@ -171,7 +171,7 @@ CREATE TABLE `eb_exam_package`
     `pic_count` int(11)      NOT NULL COMMENT '上传图片数量',
     PRIMARY KEY (`exam_id`, `code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='卷袋信息表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='卷袋信息表';
 
 
 
@@ -199,7 +199,7 @@ CREATE TABLE `eb_exam_question`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`, `is_objective`, `main_number`, `sub_number`, `paper_type`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='小题信息表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='小题信息表';
 
 
 
@@ -248,7 +248,7 @@ CREATE TABLE `eb_exam_student`
     KEY `index2` (`exam_id`, `student_code`),
     KEY `index3` (`exam_id`, `subject_code`, `upload_time`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='考试考生库';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='考试考生库';
 
 
 
@@ -275,7 +275,7 @@ CREATE TABLE `eb_exam_subject`
     `sheet_config`     text         DEFAULT NULL COMMENT '原图遮盖配置',
     PRIMARY KEY (`exam_id`, `code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='考试科目表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='考试科目表';
 
 
 
@@ -286,12 +286,12 @@ DROP TABLE IF EXISTS `eb_export_imglist`;
 
 CREATE TABLE `eb_export_imglist`
 (
-    `exam_id`     int(11)                      NOT NULL,
-    `exam_number` varchar(50) COLLATE utf8_bin NOT NULL,
-    `Remark`      varchar(255) COLLATE utf8_bin DEFAULT NULL,
+    `exam_id`     int(11) NOT NULL,
+    `exam_number` varchar(50) NOT NULL,
+    `Remark`      varchar(255) DEFAULT NULL,
     PRIMARY KEY (`exam_id`, `exam_number`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8;
+  DEFAULT CHARSET = utf8mb4;
 
 
 
@@ -321,7 +321,7 @@ CREATE TABLE `eb_mark_group`
     `third_policy`        varchar(32) DEFAULT NULL COMMENT '三评规则',
     PRIMARY KEY (`exam_id`, `subject_code`, `number`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='评卷分组表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='评卷分组表';
 
 
 
@@ -358,7 +358,7 @@ CREATE TABLE `eb_marker`
     UNIQUE KEY `index1` (`login_name`),
     KEY `index2` (`exam_id`, `subject_code`, `group_number`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='评卷员表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='评卷员表';
 
 
 # Dump of table eb_marker_class
@@ -369,12 +369,35 @@ DROP TABLE IF EXISTS `eb_marker_class`;
 CREATE TABLE `eb_marker_class`
 (
     `id`         int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
-    `class_name` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '班级名称',
+    `class_name` varchar(255) DEFAULT NULL COMMENT '班级名称',
     `marker_id`  int(11) NOT NULL COMMENT '评卷员ID',
     PRIMARY KEY (`id`),
     KEY `index1` (`marker_id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='评卷员班级表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='评卷员班级表';
+
+
+# Dump of table eb_data_sync
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `eb_data_sync`;
+CREATE TABLE `eb_data_sync` (
+  `examId` int(11) NOT NULL COMMENT '考试ID',
+  `cloud_exam_id` bigint(20) NOT NULL COMMENT '云平台考试ID',
+  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
+  `finished` tinyint(1) NOT NULL COMMENT '是否完成',
+  `next_id` bigint(20) DEFAULT NULL COMMENT '下一位考生ID',
+  `root_org_id` varchar(255) NOT NULL COMMENT '机构ID',
+  `school_id` int(11) DEFAULT NULL COMMENT '学校ID',
+  `subject_code` varchar(255) DEFAULT NULL COMMENT '科目代码',
+  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
+  `app_id` varchar(255) NOT NULL COMMENT '项目ID',
+  `secret_key` varchar(255) NOT NULL COMMENT '密钥',
+  `student_url` varchar(255) NOT NULL COMMENT '考生API URL',
+  `subject_url` varchar(255) NOT NULL COMMENT '科目API URL',
+  PRIMARY KEY (`examId`)
+) ENGINE=InnoDB 
+  DEFAULT CHARSET=utf8mb4 COMMENT ='数据同步表';
 
 
 # Dump of table m_arbitrate_history
@@ -401,7 +424,7 @@ CREATE TABLE `m_arbitrate_history`
     KEY `index2` (`user_id`, `status`, `update_time`),
     KEY `index3` (`student_id`, `status`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='仲裁记录表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='仲裁记录表';
 
 
 # Dump of table m_library
@@ -434,7 +457,7 @@ CREATE TABLE `m_library`
     UNIQUE KEY `index2` (`student_id`, `group_number`, `task_number`),
     KEY `index3` (`marker_id`, `status`, `marker_time`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='评卷任务表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='评卷任务表';
 
 
 # Dump of table m_special_tag
@@ -452,7 +475,7 @@ CREATE TABLE `m_special_tag`
     PRIMARY KEY (`id`),
     KEY `index1` (`library_id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='特殊标记表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='特殊标记表';
 
 
 # Dump of table m_track
@@ -478,7 +501,7 @@ CREATE TABLE `m_track`
     KEY `index2` (`marker_id`),
     KEY `index3` (`exam_id`, `subject_code`, `group_number`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='轨迹给分表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='轨迹给分表';
 
 
 # Dump of table m_trial_history
@@ -501,7 +524,7 @@ CREATE TABLE `m_trial_history`
     KEY `index2` (`student_id`),
     KEY `index3` (`marker_id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='试评记录表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='试评记录表';
 
 
 # Dump of table m_trial_library
@@ -522,7 +545,7 @@ CREATE TABLE `m_trial_library`
     KEY `index1` (`exam_id`, `subject_code`, `group_number`)
 ) ENGINE = InnoDB
   AUTO_INCREMENT = 4
-  DEFAULT CHARSET = utf8 COMMENT ='试评任务表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='试评任务表';
 
 
 # Dump of table m_trial_tag
@@ -542,7 +565,7 @@ CREATE TABLE `m_trial_tag`
     KEY `index2` (`marker_id`)
 ) ENGINE = InnoDB
   AUTO_INCREMENT = 4
-  DEFAULT CHARSET = utf8 COMMENT ='试评特殊标记表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='试评特殊标记表';
 
 
 # Dump of table m_trial_track
@@ -567,7 +590,7 @@ CREATE TABLE `m_trial_track`
     KEY `index2` (`student_id`),
     KEY `index3` (`marker_id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='试评轨迹表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='试评轨迹表';
 
 
 # Dump of table m_problem_type
@@ -578,18 +601,18 @@ CREATE TABLE `m_problem_type`
 (
     `id`        int(11)      NOT NULL AUTO_INCREMENT COMMENT '主键',
     `exam_id`   int(11)      NOT NULL COMMENT '考试ID',
-    `name`      varchar(128) NOT NULL COMMENT '名称',
+    `name`      varchar(128) DEFAULT NULL COMMENT '名称',
     `is_custom` tinyint(1)   NOT NULL COMMENT '是否自定义',
     PRIMARY KEY (`id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='问题类型表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='问题类型表';
 
 
 # Dump of table m_problem_history
 # ------------------------------------------------------------
 
 DROP TABLE IF EXISTS `m_problem_history`;
-CREATE TABLE `m_arbitrate_history`
+CREATE TABLE `m_problem_history`
 (
     `id`           int(11)     NOT NULL AUTO_INCREMENT COMMENT '自增主键',
     `exam_id`      int(11)     NOT NULL COMMENT '考试ID',
@@ -598,7 +621,8 @@ CREATE TABLE `m_arbitrate_history`
     `exam_number`  varchar(64) NOT NULL COMMENT '准考证号',
     `student_id`   int(11)     NOT NULL COMMENT '考生ID',
     `library_id`   int(11)     NOT NULL COMMENT '评卷任务ID',
-    `status`       int(11)     NOT NULL COMMENT '状态',
+    `status`       varchar(64)  DEFAULT NULL COMMENT '状态',
+    `problem_id`   int(11)      DEFAULT NULL COMMENT '问题ID',
     `user_id`      int(11)      DEFAULT NULL COMMENT '处理人ID',
     `total_score`  double       DEFAULT NULL COMMENT '总分',
     `score_list`   varchar(255) DEFAULT NULL COMMENT '给分明细',
@@ -609,7 +633,7 @@ CREATE TABLE `m_arbitrate_history`
     KEY `index2` (`user_id`, `status`, `update_time`),
     KEY `index3` (`student_id`, `status`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='问题卷历史表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='问题卷历史表';
 
 
 # Dump of table s_basic_group
@@ -636,7 +660,7 @@ CREATE TABLE `s_basic_class_group`
     `paper_type`           varchar(32)  DEFAULT NULL COMMENT '试卷类型',
     PRIMARY KEY (`id`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='班级大题统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='班级大题统计表';
 
 
 # Dump of table s_basic_group
@@ -671,7 +695,7 @@ CREATE TABLE `s_basic_group`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='大题统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='大题统计表';
 
 
 # Dump of table s_basic_question
@@ -707,7 +731,7 @@ CREATE TABLE `s_basic_question`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='小题统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='小题统计表';
 
 
 # Dump of table s_basic_subject
@@ -745,7 +769,7 @@ CREATE TABLE `s_basic_subject`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='科目统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='科目统计表';
 
 
 # Dump of table s_basic_subject_class
@@ -777,7 +801,7 @@ CREATE TABLE `s_basic_subject_class`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='班级统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='班级统计表';
 
 
 # Dump of table s_basic_subject_college
@@ -801,7 +825,7 @@ CREATE TABLE `s_basic_subject_college`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='学院统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='学院统计表';
 
 
 # Dump of table s_basic_subject_teacher
@@ -827,7 +851,7 @@ CREATE TABLE `s_basic_subject_teacher`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='任课老师统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='任课老师统计表';
 
 
 # Dump of table s_basic_subject_teacher_class
@@ -854,7 +878,7 @@ CREATE TABLE `s_basic_subject_teacher_class`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='任课老师班级统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='任课老师班级统计表';
 
 
 # Dump of table s_range_subject
@@ -888,4 +912,4 @@ CREATE TABLE `s_range_subject`
     PRIMARY KEY (`id`),
     KEY `index1` (`exam_id`, `subject_code`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8 COMMENT ='分段统计表';
+  DEFAULT CHARSET = utf8mb4 COMMENT ='分段统计表';

+ 506 - 0
stmms-web/src/main/webapp/static/i18n/jquery.i18n.properties.js

@@ -0,0 +1,506 @@
+/******************************************************************************
+ * jquery.i18n.properties
+ *
+ * Dual licensed under the GPL (http://dev.jquery.com/browser/trunk/jquery/GPL-LICENSE.txt) and
+ * MIT (http://dev.jquery.com/browser/trunk/jquery/MIT-LICENSE.txt) licenses.
+ *
+ * @version     1.2.7
+ * @url         https://github.com/jquery-i18n-properties/jquery-i18n-properties
+ * @inspiration Localisation assistance for jQuery (http://keith-wood.name/localisation.html)
+ *              by Keith Wood (kbwood{at}iinet.com.au) June 2007
+ *
+ *****************************************************************************/
+
+(function ($) {
+
+    $.i18n = {};
+
+    /**
+     * Map holding bundle keys if mode is 'map' or 'both'. Values of this can also be an
+     * Object, in which case the key is a namespace.
+     */
+    $.i18n.map = {};
+
+    var debug = function (message) {
+        window.console && console.log('i18n::' + message);
+    };
+
+    /**
+     * Load and parse message bundle files (.properties),
+     * making bundles keys available as javascript variables.
+     *
+     * i18n files are named <name>.js, or <name>_<language>.js or <name>_<language>_<country>.js
+     * Where:
+     *      The <language> argument is a valid ISO Language Code. These codes are the lower-case,
+     *      two-letter codes as defined by ISO-639. You can find a full list of these codes at a
+     *      number of sites, such as: http://www.loc.gov/standards/iso639-2/englangn.html
+     *      The <country> argument is a valid ISO Country Code. These codes are the upper-case,
+     *      two-letter codes as defined by ISO-3166. You can find a full list of these codes at a
+     *      number of sites, such as: http://www.iso.ch/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html
+     *
+     * Sample usage for a bundles/Messages.properties bundle:
+     * $.i18n.properties({
+     *      name:      'Messages',
+     *      language:  'en_US',
+     *      path:      'bundles'
+     * });
+     * @param  name      (string/string[], optional) names of file to load (eg, 'Messages' or ['Msg1','Msg2']). Defaults to "Messages"
+     * @param  language    (string, optional) language/country code (eg, 'en', 'en_US', 'pt_BR'). if not specified, language reported by the browser will be used instead.
+     * @param  path      (string, optional) path of directory that contains file to load
+     * @param  mode      (string, optional) whether bundles keys are available as JavaScript variables/functions or as a map (eg, 'vars' or 'map')
+     * @param  debug     (boolean, optional) whether debug statements are logged at the console
+     * @param  cache        (boolean, optional) whether bundles should be cached by the browser, or forcibly reloaded on each page load. Defaults to false (i.e. forcibly reloaded)
+     * @param  encoding  (string, optional) the encoding to request for bundles. Property file resource bundles are specified to be in ISO-8859-1 format. Defaults to UTF-8 for backward compatibility.
+     * @param  callback     (function, optional) callback function to be called after script is terminated
+     */
+    $.i18n.properties = function (settings) {
+
+        var defaults = {
+            name: 'Messages',
+            language: '',
+            path: '',
+            namespace: null,
+            mode: 'vars',
+            cache: false,
+            debug: false,
+            encoding: 'UTF-8',
+            async: false,
+            callback: null
+        };
+
+        settings = $.extend(defaults, settings);
+
+        if (settings.namespace && typeof settings.namespace == 'string') {
+            // A namespace has been supplied, initialise it.
+            if (settings.namespace.match(/^[a-z]*$/)) {
+                $.i18n.map[settings.namespace] = {};
+            } else {
+                debug('Namespaces can only be lower case letters, a - z');
+                settings.namespace = null;
+            }
+        }
+
+        // Ensure a trailing slash on the path
+        if (!settings.path.match(/\/$/)) settings.path += '/';
+
+        // Try to ensure that we have at a least a two letter language code
+        settings.language = this.normaliseLanguageCode(settings);
+
+        // Ensure an array
+        var files = (settings.name && settings.name.constructor === Array) ? settings.name : [settings.name];
+
+        // A locale is at least a language code which means at least two files per name. If
+        // we also have a country code, thats an extra file per name.
+        settings.totalFiles = (files.length * 2) + ((settings.language.length >= 5) ? files.length : 0);
+        if (settings.debug) {
+            debug('totalFiles: ' + settings.totalFiles);
+        }
+
+        settings.filesLoaded = 0;
+
+        files.forEach(function (file) {
+
+            var defaultFileName, shortFileName, longFileName, fileNames;
+            // 1. load base (eg, Messages.properties)
+            defaultFileName = settings.path + file + '.properties';
+            // 2. with language code (eg, Messages_pt.properties)
+            var shortCode = settings.language.substring(0, 2);
+            shortFileName = settings.path + file + '_' + shortCode + '.properties';
+            // 3. with language code and country code (eg, Messages_pt_BR.properties)
+            if (settings.language.length >= 5) {
+                var longCode = settings.language.substring(0, 5);
+                longFileName = settings.path + file + '_' + longCode + '.properties';
+                fileNames = [defaultFileName, shortFileName, longFileName];
+            } else {
+                fileNames = [defaultFileName, shortFileName];
+            }
+            loadAndParseFiles(fileNames, settings);
+        });
+
+        // call callback
+        if (settings.callback && !settings.async) {
+            settings.callback();
+        }
+    }; // properties
+
+    /**
+     * When configured with mode: 'map', allows access to bundle values by specifying its key.
+     * Eg, jQuery.i18n.prop('com.company.bundles.menu_add')
+     */
+    $.i18n.prop = function (key /* Add parameters as function arguments as necessary  */) {
+
+        var args = [].slice.call(arguments);
+
+        var phvList, namespace;
+        if (args.length == 2) {
+            if ($.isArray(args[1])) {
+                // An array was passed as the second parameter, so assume it is the list of place holder values.
+                phvList = args[1];
+            } else if (typeof args[1] === 'object') {
+                // Second argument is an options object {namespace: 'mynamespace', replacements: ['egg', 'nog']}
+                namespace = args[1].namespace;
+                var replacements = args[1].replacements;
+                args.splice(-1, 1);
+                if (replacements) {
+                    Array.prototype.push.apply(args, replacements);
+                }
+            }
+        }
+
+        var value = (namespace) ? $.i18n.map[namespace][key] : $.i18n.map[key];
+        if (value === null) {
+            return '[' + ((namespace) ? namespace + '#' + key : key) + ']';
+        }
+
+        // Place holder replacement
+        /**
+        * Tested with:
+        *   test.t1=asdf ''{0}''
+        *   test.t2=asdf '{0}' '{1}'{1}'zxcv
+        *   test.t3=This is \"a quote" 'a''{0}''s'd{fgh{ij'
+        *   test.t4="'''{'0}''" {0}{a}
+        *   test.t5="'''{0}'''" {1}
+        *   test.t6=a {1} b {0} c
+        *   test.t7=a 'quoted \\ s\ttringy' \t\t x
+        *
+        * Produces:
+        *   test.t1, p1 ==> asdf 'p1'
+        *   test.t2, p1 ==> asdf {0} {1}{1}zxcv
+        *   test.t3, p1 ==> This is "a quote" a'{0}'sd{fgh{ij
+        *   test.t4, p1 ==> "'{0}'" p1{a}
+        *   test.t5, p1 ==> "'{0}'" {1}
+        *   test.t6, p1 ==> a {1} b p1 c
+        *   test.t6, p1, p2 ==> a p2 b p1 c
+        *   test.t6, p1, p2, p3 ==> a p2 b p1 c
+        *   test.t7 ==> a quoted \ s	tringy 		 x
+        */
+
+        var i;
+        if (typeof(value) == 'string') {
+            // Handle escape characters. Done separately from the tokenizing loop below because escape characters are
+            // active in quoted strings.
+            i = 0;
+            while ((i = value.indexOf('\\', i)) != -1) {
+                if (value.charAt(i + 1) == 't') {
+                    value = value.substring(0, i) + '\t' + value.substring((i++) + 2); // tab
+                } else if (value.charAt(i + 1) == 'r') {
+                    value = value.substring(0, i) + '\r' + value.substring((i++) + 2); // return
+                } else if (value.charAt(i + 1) == 'n') {
+                    value = value.substring(0, i) + '\n' + value.substring((i++) + 2); // line feed
+                } else if (value.charAt(i + 1) == 'f') {
+                    value = value.substring(0, i) + '\f' + value.substring((i++) + 2); // form feed
+                } else if (value.charAt(i + 1) == '\\') {
+                    value = value.substring(0, i) + '\\' + value.substring((i++) + 2); // \
+                } else {
+                    value = value.substring(0, i) + value.substring(i + 1); // Quietly drop the character
+                }
+            }
+
+            // Lazily convert the string to a list of tokens.
+            var arr = [], j, index;
+            i = 0;
+            while (i < value.length) {
+                if (value.charAt(i) == '\'') {
+                    // Handle quotes
+                    if (i == value.length - 1) {
+                        value = value.substring(0, i); // Silently drop the trailing quote
+                    } else if (value.charAt(i + 1) == '\'') {
+                        value = value.substring(0, i) + value.substring(++i); // Escaped quote
+                    } else {
+                        // Quoted string
+                        j = i + 2;
+                        while ((j = value.indexOf('\'', j)) != -1) {
+                            if (j == value.length - 1 || value.charAt(j + 1) != '\'') {
+                                // Found start and end quotes. Remove them
+                                value = value.substring(0, i) + value.substring(i + 1, j) + value.substring(j + 1);
+                                i = j - 1;
+                                break;
+                            } else {
+                                // Found a double quote, reduce to a single quote.
+                                value = value.substring(0, j) + value.substring(++j);
+                            }
+                        }
+
+                        if (j == -1) {
+                            // There is no end quote. Drop the start quote
+                            value = value.substring(0, i) + value.substring(i + 1);
+                        }
+                    }
+                } else if (value.charAt(i) == '{') {
+                    // Beginning of an unquoted place holder.
+                    j = value.indexOf('}', i + 1);
+                    if (j == -1) {
+                        i++; // No end. Process the rest of the line. Java would throw an exception
+                    } else {
+                        // Add 1 to the index so that it aligns with the function arguments.
+                        index = parseInt(value.substring(i + 1, j));
+                        if (!isNaN(index) && index >= 0) {
+                            // Put the line thus far (if it isn't empty) into the array
+                            var s = value.substring(0, i);
+                            if (s !== "") {
+                                arr.push(s);
+                            }
+                            // Put the parameter reference into the array
+                            arr.push(index);
+                            // Start the processing over again starting from the rest of the line.
+                            i = 0;
+                            value = value.substring(j + 1);
+                        } else {
+                            i = j + 1; // Invalid parameter. Leave as is.
+                        }
+                    }
+                } else {
+                    i++;
+                }
+            } // while
+
+            // Put the remainder of the no-empty line into the array.
+            if (value !== "") {
+                arr.push(value);
+            }
+            value = arr;
+
+            // Make the array the value for the entry.
+            if (namespace) {
+                $.i18n.map[settings.namespace][key] = arr;
+            } else {
+                $.i18n.map[key] = arr;
+            }
+        }
+
+        if (value.length === 0) {
+            return "";
+        }
+        if (value.length == 1 && typeof(value[0]) == "string") {
+            return value[0];
+        }
+
+        var str = "";
+        for (i = 0, j = value.length; i < j; i++) {
+            if (typeof(value[i]) == "string") {
+                str += value[i];
+            } else if (phvList && value[i] < phvList.length) {
+                // Must be a number
+                str += phvList[value[i]];
+            } else if (!phvList && value[i] + 1 < args.length) {
+                str += args[value[i] + 1];
+            } else {
+                str += "{" + value[i] + "}";
+            }
+        }
+
+        return str;
+    };
+
+    function callbackIfComplete(settings) {
+
+        if (settings.debug) {
+            debug('callbackIfComplete()');
+            debug('totalFiles: ' + settings.totalFiles);
+            debug('filesLoaded: ' + settings.filesLoaded);
+        }
+
+        if (settings.async) {
+            if (settings.filesLoaded === settings.totalFiles) {
+                if (settings.callback) {
+                    settings.callback();
+                }
+            }
+        }
+    }
+
+    function loadAndParseFiles(fileNames, settings) {
+
+        if (settings.debug) debug('loadAndParseFiles');
+
+	    if (fileNames !== null && fileNames.length > 0) {
+		    loadAndParseFile(fileNames[0], settings, function () {
+			    fileNames.shift();
+			    loadAndParseFiles(fileNames,settings);
+		    });
+	    } else {
+            callbackIfComplete(settings);
+        }
+    }
+
+    /** Load and parse .properties files */
+    function loadAndParseFile(filename, settings, nextFile) {
+
+        if (settings.debug) {
+            debug('loadAndParseFile(\'' + filename +'\')');
+            debug('totalFiles: ' + settings.totalFiles);
+            debug('filesLoaded: ' + settings.filesLoaded);
+        }
+
+  	    if (filename !== null && typeof filename !== 'undefined') {
+            $.ajax({
+                url: filename,
+                async: settings.async,
+                cache: settings.cache,
+                dataType: 'text',
+                success: function (data, status) {
+
+                    if (settings.debug) {
+                        debug('Succeeded in downloading ' + filename + '.');
+                        debug(data);
+                    }
+
+                    parseData(data, settings);
+                    nextFile();
+                },
+                error: function (jqXHR, textStatus, errorThrown) {
+
+                    if (settings.debug) {
+                        debug('Failed to download or parse ' + filename + '. errorThrown: ' + errorThrown);
+                    }
+                    if (jqXHR.status === 404) {
+                        settings.totalFiles -= 1;
+                    }
+                    nextFile();
+                }
+            });
+        }
+    }
+
+    /** Parse .properties files */
+    function parseData(data, settings) {
+
+        var parsed = '';
+        var lines = data.split(/\n/);
+        var regPlaceHolder = /(\{\d+})/g;
+        var regRepPlaceHolder = /\{(\d+)}/g;
+        var unicodeRE = /(\\u.{4})/ig;
+        for (var i=0,j=lines.length;i<j;i++) {
+            var line = lines[i];
+
+            line = line.trim();
+            if (line.length > 0 && line.match("^#") != "#") { // skip comments
+                var pair = line.split('=');
+                if (pair.length > 0) {
+                    /** Process key & value */
+                    var name = decodeURI(pair[0]).trim();
+                    var value = pair.length == 1 ? "" : pair[1];
+                    // process multi-line values
+                    while (value.search(/\\$/) != -1) {
+                        value = value.substring(0, value.length - 1);
+                        value += lines[++i].trimRight();
+                    }
+                    // Put values with embedded '='s back together
+                    for (var s = 2; s < pair.length; s++) {
+                        value += '=' + pair[s];
+                    }
+                    value = value.trim();
+
+                    /** Mode: bundle keys in a map */
+                    if (settings.mode == 'map' || settings.mode == 'both') {
+                        // handle unicode chars possibly left out
+                        var unicodeMatches = value.match(unicodeRE);
+                        if (unicodeMatches) {
+                            unicodeMatches.forEach(function (match) {
+                                value = value.replace(match, unescapeUnicode(match));
+                            });
+                        }
+                        // add to map
+                        if (settings.namespace) {
+                            $.i18n.map[settings.namespace][name] = value;
+                        } else {
+                            $.i18n.map[name] = value;
+                        }
+                    }
+
+                    /** Mode: bundle keys as vars/functions */
+                    if (settings.mode == 'vars' || settings.mode == 'both') {
+                        value = value.replace(/"/g, '\\"'); // escape quotation mark (")
+
+                        // make sure namespaced key exists (eg, 'some.key')
+                        checkKeyNamespace(name);
+
+                        // value with variable substitutions
+                        if (regPlaceHolder.test(value)) {
+                            var parts = value.split(regPlaceHolder);
+                            // process function args
+                            var first = true;
+                            var fnArgs = '';
+                            var usedArgs = [];
+                            parts.forEach(function (part) {
+
+                                if (regPlaceHolder.test(part) && (usedArgs.length === 0 || usedArgs.indexOf(part) == -1)) {
+                                    if (!first) {
+                                        fnArgs += ',';
+                                    }
+                                    fnArgs += part.replace(regRepPlaceHolder, 'v$1');
+                                    usedArgs.push(part);
+                                    first = false;
+                                }
+                            });
+                            parsed += name + '=function(' + fnArgs + '){';
+                            // process function body
+                            var fnExpr = '"' + value.replace(regRepPlaceHolder, '"+v$1+"') + '"';
+                            parsed += 'return ' + fnExpr + ';' + '};';
+                            // simple value
+                        } else {
+                            parsed += name + '="' + value + '";';
+                        }
+                    } // END: Mode: bundle keys as vars/functions
+                } // END: if(pair.length > 0)
+            } // END: skip comments
+        }
+        eval(parsed);
+        settings.filesLoaded += 1;
+    }
+
+    /** Make sure namespace exists (for keys with dots in name) */
+    // TODO key parts that start with numbers quietly fail. i.e. month.short.1=Jan
+    function checkKeyNamespace(key) {
+
+        var regDot = /\./;
+        if (regDot.test(key)) {
+            var fullname = '';
+            var names = key.split(/\./);
+            for (var i=0,j=names.length;i<j;i++) {
+                var name = names[i];
+
+                if (i > 0) {
+                    fullname += '.';
+                }
+
+                fullname += name;
+                if (eval('typeof ' + fullname + ' == "undefined"')) {
+                    eval(fullname + '={};');
+                }
+            }
+        }
+    }
+
+    /** Ensure language code is in the format aa_AA. */
+    $.i18n.normaliseLanguageCode = function (settings) {
+
+        var lang = settings.language;
+        if (!lang || lang.length < 2) {
+            if (settings.debug) debug('No language supplied. Pulling it from the browser ...');
+            lang = (navigator.languages && navigator.languages.length > 0) ? navigator.languages[0]
+                                        : (navigator.language || navigator.userLanguage /* IE */ || 'en');
+            if (settings.debug) debug('Language from browser: ' + lang);
+        }
+
+        lang = lang.toLowerCase();
+        lang = lang.replace(/-/,"_"); // some browsers report language as en-US instead of en_US
+        if (lang.length > 3) {
+            lang = lang.substring(0, 3) + lang.substring(3).toUpperCase();
+        }
+        return lang;
+    };
+
+    /** Unescape unicode chars ('\u00e3') */
+    function unescapeUnicode(str) {
+
+        // unescape unicode codes
+        var codes = [];
+        var code = parseInt(str.substr(2), 16);
+        if (code >= 0 && code < Math.pow(2, 16)) {
+            codes.push(code);
+        }
+        // convert codes to text
+        return codes.reduce(function (acc, val) { return acc + String.fromCharCode(val); }, '');
+    }
+}) (jQuery);

+ 37 - 0
stmms-web/src/main/webapp/static/i18n/load.js

@@ -0,0 +1,37 @@
+$(document).ready(function() {
+	load();
+})
+function load() {  
+	var language = (navigator.browserLanguage || navigator.language).toLowerCase();
+	if(language.indexOf('zh')>-1){
+		loadProperties('zh');
+	}else if(language.indexOf('en')>-1){
+		loadProperties('en');
+	}else if(language.indexOf('ja')>-1){
+		loadProperties('ja');
+	}else{
+		loadProperties('zh');
+	}
+	function loadProperties(locale) {  
+		jQuery.i18n.properties({//加载资浏览器语言对应的资源文件
+			name : 'messages', //资源文件名称
+			path : '/resources/i18n/', //资源文件路径
+			mode : 'map', //用Map的方式使用资源文件中的值
+			language : locale,
+			callback : function() {//加载成功后设置显示内容
+				$("[data-i18n-value]").each(function() {
+					$(this).val($.i18n.prop($(this).data("i18n-value")));
+				});
+				$("[data-i18n-text]").each(function() {
+					$(this).text($.i18n.prop($(this).data("i18n-text")));
+				});
+				$("[data-i18n-html]").each(function() {
+					$(this).html($.i18n.prop($(this).data("i18n-html")));
+				});
+				$("[data-i18n-placeholder]").each(function() {
+					$(this).attr('placeholder',$.i18n.prop($(this).data("i18n-placeholder")));
+				});
+			}
+		});			
+	}
+}

+ 138 - 0
stmms-web/src/main/webapp/static/i18n/messages.properties

@@ -0,0 +1,138 @@
+#login
+user.login.title=\u9ad8\u6821\u8003\u8bd5\u7ba1\u7406\u5e73\u53f0
+user.login.admin=\u7ba1\u7406\u5458\u767b\u5f55
+user.login.marker=\u8bc4\u5377\u5458\u767b\u5f55
+user.login.name=\u7528\u6237\u540d
+user.login.password=\u5bc6\u7801
+user.login.submit=\u767b\u5f55
+user.login.error.account=\u5e10\u53f7\u4e0d\u5b58\u5728
+user.login.error.finish=\u8bc4\u5377\u5df2\u7ed3\u675f
+user.login.error.group=\u5927\u9898\u4e0d\u5b58\u5728
+user.login.error.disabled=\u5e10\u53f7\u5df2\u7981\u7528
+user.login.error.password=\u5bc6\u7801\u9519\u8bef
+user.login.error.access=\u7528\u6237\u6ca1\u6709\u8bbf\u95ee\u6743\u9650
+#reset
+user.reset.submit=\u786e\u5b9a
+user.reset.logout=\u9000\u51fa
+user.reset.title=\u9996\u6b21\u767b\u5f55\uff0c\u8bf7\u5b8c\u5584\u8d44\u6599
+user.reset.name=\u8f93\u5165\u7528\u6237\u540d
+user.reset.password=\u8f93\u5165\u65b0\u5bc6\u7801
+user.reset.password.again=\u518d\u6b21\u8f93\u5165\u65b0\u5bc6\u7801
+user.reset.name.length=\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc710\u4e2a\u5b57
+user.reset.password.same=\u4e24\u6b21\u5bc6\u7801\u8bf7\u4fdd\u6301\u4e00\u81f4
+user.reset.password.length=\u5bc6\u7801\u7684\u957f\u5ea6\u81f3\u5c114\u4f4d\uff0c\u4e0d\u80fd\u8d85\u8fc78\u4f4d
+#mark-control
+mark.control.assistant=\u5c0f\u52a9\u624b
+mark.control.mode.track=\u5207\u6362\u5230\u8f68\u8ff9\u6a21\u5f0f
+mark.control.mode.common=\u5207\u6362\u5230\u666e\u901a\u6a21\u5f0f
+mark.control.function=\u8bc4\u5377\u529f\u80fd
+mark.control.logout=\u9000\u51fa
+mark.control.init.error=\u521d\u59cb\u5316\u5931\u8d25\uff0c\u8bf7\u5237\u65b0\u9875\u9762\u91cd\u65b0\u52a0\u8f7d
+mark.control.task.not.exist=\u8bc4\u5377\u5927\u9898\u4e0d\u5b58\u5728
+mark.control.task.finish=\u8bc4\u5377\u5df2\u7ed3\u675f
+mark.control.task.null=\u5f53\u524d\u65e0\u8bc4\u5377\u4efb\u52a1
+mark.control.tsak.error=\u8bc4\u5377\u4efb\u52a1\u63d0\u4ea4\u5931\u8d25\uff0c\u8bf7\u5237\u65b0\u9875\u9762
+#status
+mark.status.student.number=\u8003\u751f\u7f16\u53f7
+mark.status.library.number=\u4efb\u52a1\u7f16\u53f7
+mark.status.objective.score=\u5ba2\u89c2\u5f97\u5206
+mark.status.marked.count=\u5df2\u8bc4
+mark.status.unmark.count=\u672a\u8bc4
+mark.status.top.count=\u5206\u914d
+mark.status.progress=\u8fdb\u5ea6
+mark.status.continue=\u7ee7\u7eed
+mark.status.top.count.finish=\u5206\u914d\u4efb\u52a1\u5df2\u8bc4\u5b8c\uff0c\u662f\u5426\u7ee7\u7eed\uff1f
+mark.status.loading=\u6b63\u5728\u52a0\u8f7d...
+mark.status.logout=\u9000\u51fa
+#single-image-view
+mark.single.zoom.in=\u653e\u5927
+mark.single.zoom.out=\u7f29\u5c0f
+mark.single.zoom.fit=\u9002\u5e94
+mark.single.student.answer=\u7b54\u5377
+#chang name
+mark.change.name=\u4fee\u6539\u4e2a\u4eba\u4fe1\u606f
+mark.change.press.name=\u8f93\u5165\u7528\u6237\u540d
+mark.change.press.password=\u8f93\u5165\u65b0\u5bc6\u7801
+mark.change.press.password.again=\u518d\u6b21\u8f93\u5165\u65b0\u5bc6\u7801
+mark.change.confirm=\u786e\u5b9a
+mark.change.name.null=\u540d\u5b57\u4e0d\u80fd\u4e3a\u7a7a
+mark.change.name.length=\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc78\u4e2a\u5b57
+mark.change.password.same=\u4e24\u6b21\u5bc6\u7801\u8bf7\u4fdd\u6301\u4e00\u81f4
+mark.change.password.length=\u5bc6\u7801\u7684\u957f\u5ea6\u81f3\u5c114\u4f4d
+mark.change.network.error=\u7f51\u7edc\u901a\u4fe1\u9519\u8bef\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5
+mark.change.error=\u4fee\u6539\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5
+#specialTag
+mark.special.tag=\u7279\u6b8a\u6807\u8bb0
+mark.special.back=\u56de\u9000
+mark.special.clear=\u5168\u90e8\u6e05\u9664
+mark.special.underline=\u4e0b\u5212\u7ebf
+mark.special.open=\u6253\u5f00
+mark.special.close=\u5173\u95ed
+#problem-process
+mark.problem=\u95ee\u9898\u5377
+mark.problem.type=\u9009\u62e9\u95ee\u9898\u7c7b\u578b
+mark.problem.confirm=\u786e\u5b9a
+mark.problem.cancel=\u53d6\u6d88
+mark.problem.check=\u8bf7\u9009\u62e9\u95ee\u9898\u5206\u7c7b
+#thumbnail
+mark.thumbnail=\u7f29\u7565\u56fe
+mark.thumbnail.open=\u6253\u5f00
+mark.thumbnail.close=\u5173\u95ed
+#view-sidebar
+mark.sidebar=\u4fa7\u8fb9\u680f
+#mark-history
+mark.history.title=\u56de\u8bc4
+mark.history.time=\u65f6\u95f4
+mark.history.number=\u7f16\u53f7
+mark.history.score=\u603b\u5206
+mark.history.search=\u67e5\u627e\u8bd5\u5377
+mark.history.pre=\u524d
+mark.history.success=\u56de\u8bc4\u6210\u529f\uff0c\u603b\u5206\uff1a
+mark.history.problem=\u56de\u8bc4\u6210\u529f\uff0c\u5df2\u63d0\u4ea4\u95ee\u9898\u5377
+mark.history.loading=\u6b63\u5728\u52a0\u8f7d\u8bf7\u7a0d\u5019
+mark.history.error=\u6682\u65f6\u65e0\u6cd5\u8bfb\u53d6\u8bc4\u5377\u5386\u53f2\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5
+mark.history.number.error=\u8bf7\u8f93\u5165\u6570\u5b57
+#mark-board
+mark.board.submit=\u63d0\u4ea4
+mark.board.total.score=\u603b\u5206
+mark.board.score.zero=\u5168\u96f6\u5206
+mark.board.pass=\u8df3 \u8fc7
+mark.board.choose.result=\u9009\u505a\u7ed3\u679c
+mark.board.keyboard=\u952e\u76d8\u7ed9\u5206 >>
+mark.board.mouse=<< \u9f20\u6807\u7ed9\u5206
+mark.board.interval=\u95f4\u9694
+mark.board.score=\u5206
+mark.board.back=\u56de\u9000
+mark.board.clear.question=\u6e05\u9664\u672c\u9898
+mark.board.clear.question=\u6e05\u9664\u5168\u5377
+mark.board.question.not.mark=\u5f53\u524d\u4efb\u52a1\u8fd8\u6709\u672a\u7ed9\u5206\u7684\u9898\uff0c\u8bf7\u7ee7\u7eed\u7ed9\u5206
+mark.board.confirm=,\u786e\u8ba4\u63d0\u4ea4\u5417\uff1f
+mark.board.zero.confirm=\u786e\u8ba4\u8981\u63d0\u4ea4\u5168\u96f6\u5206\u5417\uff1f
+mark.board.null=\u65e0
+mark.board.interval.error=\u4e0d\u7b26\u5408\u5206\u503c\u95f4\u9694\u8981\u6c42
+mark.board.gt=\u4e0d\u80fd\u5927\u4e8e
+mark.board.lt=\u4e0d\u80fd\u5c0f\u4e8e
+mark.board.number.error=\u4e0d\u662f\u5408\u6cd5\u6570\u5b57
+#sheet-view
+mark.sheet=\u539f\u56fe
+mark.sheet.check=\u539f\u56fe\u5207\u6362
+mark.sheet.open=\u6253\u5f00
+mark.sheet.close=\u5173\u95ed
+#answer-view
+mark.answer=\u6807\u7b54
+#warning-info
+mark.warning.try.again=\u8bf7\u70b9\u51fb\u91cd\u8bd5
+mark.warning.force.special.tag=\u5f3a\u5236\u7279\u6b8a\u6807\u8bb0\u5df2\u5f00\u542f\uff0c\u81f3\u5c11\u4f7f\u7528\u4e00\u4e2a\u7279\u6b8a\u6807\u8bb0
+mark.warning.network.error=\u7f51\u7edc\u5f02\u5e38\uff0c\u4efb\u52a1\u63d0\u4ea4\u5931\u8d25
+mark.warning.task.error=\u9886\u53d6\u8bc4\u5377\u4efb\u52a1\u51fa\u9519
+mark.warning.task.finish=\u8bc4\u5377\u4efb\u52a1\u5df2\u5b8c\u6210
+mark.warning.task.loading=\u8bc4\u5377\u4efb\u52a1\u6b63\u5728\u52a0\u8f7d\u4e2d
+mark.warning.close=\u5173\u95ed
+mark.warning.success=\u56de\u8bc4\u6210\u529f\uff0c\u603b\u5206\uff1a
+mark.warning.problem=\u56de\u8bc4\u95ee\u9898\u5377\u6210\u529f
+#json-view
+mark.json.loading=\u6b63\u5728\u52a0\u8f7d\u4e2d
+mark.json.student.answer=\u8003\u751f\u7b54\u6848\uff1a
+mark.json.answer=\u6807\u7b54\uff1a
+mark.json.body=\u9898\u5e72\uff1a
+mark.json.question.number=\u9898\u53f7\uff1a

+ 138 - 0
stmms-web/src/main/webapp/static/i18n/messages_en.properties

@@ -0,0 +1,138 @@
+#login
+user.login.title=Marking System
+user.login.admin=Admin
+user.login.marker=Marker
+user.login.name=username
+user.login.password=password
+user.login.submit=Login
+user.login.error.account=Account does not exist
+user.login.error.finish=marking is finish
+user.login.error.group=the question group is not exist
+user.login.error.disabled=Account disabled
+user.login.error.password=Password error
+user.login.error.access=User does not have access
+#reset
+user.reset.submit=Submit
+user.reset.logout=Logout
+user.reset.title=Supplement personal information
+user.reset.name=press your name
+user.reset.password=press new password
+user.reset.password.again=press new password again
+user.reset.name.length=length cannot exceed 10 words
+user.reset.password.same=please keep the same password twice
+user.reset.password.length=password must be at least 4 word,cannot exceed 8 words
+#mark-control
+mark.control.assistant=Assistant
+mark.control.mode.track=Track Mode
+mark.control.mode.common=Common Mode
+mark.control.function=Marking function
+mark.control.logout=logout
+mark.control.init.error=Initialization failed, please refresh the page!
+mark.control.task.not.exist=the task question is not exist
+mark.control.task.finish=the marking is finished
+mark.control.task.null=There is no task now
+mark.control.tsak.error=task submit failed, please refresh the page!
+#status
+mark.status.student.number=student number
+mark.status.library.number=task number
+mark.status.objective.score=objective score
+mark.status.marked.count=done
+mark.status.unmark.count=todo
+mark.status.top.count=assigned
+mark.status.progress=progress
+mark.status.continue=continue
+mark.status.top.count.finish=The assigned task has been completed. Do you want to continue?
+mark.status.loading=loading...
+mark.status.logout=logout
+#single-image-view
+mark.single.zoom.in=Zoom in
+mark.single.zoom.out=Zoom out
+mark.single.zoom.fit=Zoom fit
+mark.single.student.answer=student answer
+#chang name
+mark.change.name=change userInfo
+mark.change.press.name=press your name
+mark.change.press.password=press new password
+mark.change.press.password.again=press new password again
+mark.change.confirm=ok
+mark.change.name.null=name cannot be empty
+mark.change.name.length=length cannot exceed 8 words
+mark.change.password.same=please keep the same password twice
+mark.change.password.length=password must be at least 4 word
+mark.change.network.error=network error.please try again later
+mark.change.error=change error.please try again later
+#specialTag
+mark.special.tag=Special tag
+mark.special.back=back
+mark.special.clear=clear all
+mark.special.underline=underline
+mark.special.open=open
+mark.special.close=close
+#problem-process
+mark.problem=Problem
+mark.problem.type=check problem type
+mark.problem.confirm=ok
+mark.problem.cancel=cancel
+mark.problem.check=please check problem type
+#thumbnail
+mark.thumbnail=thumbnail
+mark.thumbnail.open=open
+mark.thumbnail.close=close
+#view-sidebar
+mark.sidebar=sidebar
+#mark-history
+mark.history.title=history
+mark.history.time=time
+mark.history.number=number
+mark.history.score=score
+mark.history.search=search
+mark.history.pre=pre
+mark.history.success=successful,score: 
+mark.history.problem=submit problem successful 
+mark.history.loading=loading..
+mark.history.error=Unable to read the history at this time. Please try again later
+mark.history.number.error=please press number
+#mark-board
+mark.board.submit=submit
+mark.board.total.score=Score\uff1a
+mark.board.score.zero=all zero
+mark.board.pass=pass
+mark.board.choose.result=choose result
+mark.board.keyboard=mark by keyboard >>
+mark.board.mouse=<< mark by mouse
+mark.board.interval=interval:
+mark.board.score=
+mark.board.back=back
+mark.board.clear.question=clear
+mark.board.clear=clear all
+mark.board.question.not.mark=There are still questions not mark, please continue
+mark.board.confirm=,Are you sure to submit?
+mark.board.zero.confirm=Are you sure to submit all zero?
+mark.board.null=
+mark.board.interval.error=score interval error
+mark.board.gt=cannot be greater than
+mark.board.lt=cannot be less than
+mark.board.number.error=not a number
+#sheet-view
+mark.sheet=sheet
+mark.sheet.check=check sheet
+mark.sheet.open=open
+mark.sheet.close=close
+#answer-view
+mark.answer=answer
+#warning-info
+mark.warning.try.again=please click try again
+mark.warning.force.special.tag=Force special tag is open, please use at least one tag
+mark.warning.network.error=network error,get task failed
+mark.warning.task.error=get task failed
+mark.warning.task.finish=marking task finished
+mark.warning.task.loading=task loading
+mark.warning.close=close
+mark.warning.success=submit success,score: 
+mark.warning.problem=submit problem success
+#json-view
+mark.json.loading=loading...
+mark.json.student.answer=student answer:
+mark.json.answer=answer:
+mark.json.body=question:
+mark.json.question.number=question number:

+ 138 - 0
stmms-web/src/main/webapp/static/i18n/messages_ja.properties

@@ -0,0 +1,138 @@
+#login
+user.login.title=\u30de\u30fc\u30ad\u30f3\u30b0\u30b7\u30b9\u30c6\u30e0
+user.login.admin=\u7ba1\u7406\u4eba\u767b\u9332
+user.login.marker=\u63a1\u70b9\u54e1\u767b\u9332
+user.login.name=\u30e6\u30fc\u30b6\u30fc\u30cd\u30fc\u30e0
+user.login.password=\u30d1\u30b9\u30ef\u30fc\u30c9
+user.login.submit=\u30ed\u30b0\u30a4\u30f3
+user.login.error.account=\u8a72\u5f53\u3059\u308b\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u5b58\u5728\u3057\u307e\u305b\u3093
+user.login.error.finish=\u30de\u30fc\u30ad\u30f3\u30b0\u306f\u7d42\u4e86\u3057\u307e\u3057\u305f
+user.login.error.group=\u8a72\u5f53\u3059\u308b\u30b0\u30eb\u30fc\u30d7\u306f\u5b58\u5728\u3057\u307e\u305b\u3093
+user.login.error.disabled=\u30a2\u30ab\u30a6\u30f3\u30c8\u306f\u7981\u6b62\u3055\u308c\u305f
+user.login.error.password=\u30d1\u30b9\u30ef\u30fc\u30c9\u30a8\u30e9\u30fc
+user.login.error.access=\u30e6\u30fc\u30b6\u30fc\u306f\u30b7\u30b9\u30c6\u30e0\u306b\u30a2\u30af\u30bb\u30b9\u3059\u308b\u6a29\u9650\u3092\u6301\u3061\u307e\u305b\u3093
+#reset
+user.reset.submit=\u78ba\u8a8d
+user.reset.logout=\u30ed\u30b0\u30a2\u30a6\u30c8
+user.reset.title=\u521d\u30ed\u30b0\u30a4\u30f3\u306a\u306e\u3067\uff0c\u8cc7\u6599\u3092\u5b8c\u5099\u3057\u3066\u4e0b\u3055\u3044
+user.reset.name=\u30e6\u30fc\u30b6\u30fc\u30cd\u30fc\u30e0\u3092\u5165\u529b\u4e0b\u3055\u3044
+user.reset.password=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u4e0b\u3055\u3044
+user.reset.password.again=\u518d\u3073\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u4e0b\u3055\u3044
+user.reset.name.length=\u9577\u3055\u306f8\u6841\u3092\u8d85\u3048\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+user.reset.password.same=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u4e00\u81f4\u3057\u3066\u4e0b\u3055\u3044
+user.reset.password.length=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u9577\u3055\u306f\uff14\u6841\u4ee5\u4e0a\u3001\uff18\u6841\u4ee5\u4e0b\u3068\u306a\u308a\u307e\u3059
+#mark-control
+mark.control.assistant=\u30a2\u30b7\u30b9\u30bf\u30f3\u30c8
+mark.control.mode.track=\u8ecc\u8de1\u30e2\u30fc\u30c9\u3078
+mark.control.mode.common=\u30ce\u30fc\u30de\u30eb\u30e2\u30fc\u30c9\u3078
+mark.control.function=\u30de\u30fc\u30ad\u30f3\u30b0\u529f\u80fd
+mark.control.logout=\u30ed\u30b0\u30a2\u30a6\u30c8
+mark.control.init.error=\u521d\u671f\u5316\u5931\u6557\u3057\u305f\u306e\u3067\u3001\u30a6\u30a7\u30d6\u30da\u30fc\u30b8\u3092\u30ea\u30ed\u30fc\u30c9\u4e0b\u3055\u3044
+mark.control.task.not.exist=\u8a72\u5f53\u3059\u308b\u984c\u76ee\u306f\u898b\u5f53\u305f\u308a\u307e\u305b\u3093
+mark.control.task.finish=\u30de\u30fc\u30ad\u30f3\u30b0\u306f\u7d42\u4e86\u3057\u307e\u3057\u305f
+mark.control.task.null=\u8a72\u5f53\u3059\u308b\u30bf\u30b9\u30af\u306f\u898b\u5f53\u305f\u308a\u307e\u305b\u3093
+mark.control.tsak.error=\u30bf\u30b9\u30af\u306e\u63d0\u51fa\u306f\u5931\u6557\u3057\u305f\u3001\u30a6\u30a7\u30d6\u30da\u30fc\u30b8\u3092\u30ea\u30d5\u30ec\u30c3\u30b7\u30e5\u4e0b\u3055\u3044
+#status
+mark.status.student.number=\u53d7\u9a13\u751f\u756a\u53f7
+mark.status.library.number=\u30bf\u30b9\u30af\u756a\u53f7
+mark.status.objective.score=\u30aa\u30d6\u30b8\u30a7\u30af\u30c6\u30a3\u30d6\u30b9\u30b3\u30a2
+mark.status.marked.count=\u63a1\u70b9\u6e08\u307f
+mark.status.unmark.count=\u63a1\u70b9\u306a\u3057
+mark.status.top.count=\u5272\u308a\u5f53\u3066
+mark.status.progress=\u9032\u5ea6
+mark.status.continue=\u7d9a\u884c\u3059\u308b
+mark.status.top.count.finish=\u5272\u308a\u5f53\u3066\u305f\u4efb\u52d9\u304c\u5b8c\u6210\u3057\u305f\u306e\u3067\u3001\u7d9a\u884c\u3057\u307e\u3059\u304b\uff1f
+mark.status.loading=\u30ed\u30fc\u30c7\u30a3\u30f3\u30b0...
+mark.status.logout=\u30ed\u30b0\u30a2\u30a6\u30c8
+#single-image-view
+mark.single.zoom.in=\u30ba\u30fc\u30e0\u30a4\u30f3
+mark.single.zoom.out=\u30ba\u30fc\u30e0\u30a2\u30a6\u30c8
+mark.single.zoom.fit=\u9069\u5fdc
+mark.single.student.answer=\u8a66\u9a13\u7528\u7d19
+#chang name
+mark.change.name=\u500b\u4eba\u60c5\u5831\u306e\u5909\u66f4
+mark.change.press.name=\u30e6\u30fc\u30b6\u30fc\u30cd\u30fc\u30e0\u3092\u5165\u529b\u4e0b\u3055\u3044
+mark.change.press.password=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b\u4e0b\u3055\u3044
+mark.change.press.password.again=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u518d\u3073\u5165\u529b\u4e0b\u3055\u3044
+mark.change.confirm=\u78ba\u5b9a
+mark.change.name.null=\u30e6\u30fc\u30b6\u30fc\u30cd\u30fc\u30e0\u306f\u7a7a\u306b\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093
+mark.change.name.length=\u30e6\u30fc\u30b6\u30fc\u30cd\u30fc\u30e0\u306e\u9577\u3055\u306f8\u6841\u3092\u8d85\u3048\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093
+mark.change.password.same=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u4e00\u81f4\u3057\u3066\u4e0b\u3055\u3044
+mark.change.password.length=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u9577\u3055\u306f\u5c11\u306a\u304f\u3068\u3082\uff14\u6841
+mark.change.network.error=\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u3078\u306e\u901a\u4fe1\u304c\u5931\u6557\u3057\u305f\uff0c\u5f8c\u3067\u3084\u308a\u76f4\u3057\u3066\u4e0b\u3055\u3044
+mark.change.error=\u5909\u66f4\u304c\u5931\u6557\u3057\u305f\uff0c\u5f8c\u3067\u3084\u308a\u76f4\u3057\u3066\u4e0b\u3055\u3044
+#specialTag
+mark.special.tag=\u7279\u6b8a\u306a\u76ee\u5370
+mark.special.back=\u30d0\u30c3\u30af
+mark.special.clear=\u3059\u3079\u3066\u30af\u30fc\u30ea\u30a2
+mark.special.underline=\u30a2\u30f3\u30c0\u30fc\u30e9\u30a4\u30f3
+mark.special.open=\u30aa\u30fc\u30d7\u30f3
+mark.special.close=\u30af\u30ed\u30fc\u30ba
+#problem-process
+mark.problem=\u554f\u984c\u30bf\u30a4\u30d7
+mark.problem.type=\u554f\u984c\u30bf\u30a4\u30d7\u306e\u9078\u629e
+mark.problem.confirm=\u78ba\u5b9a
+mark.problem.cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
+mark.problem.check=\u554f\u984c\u30bf\u30a4\u30d7\u3092\u9078\u629e\u3057\u3066\u4e0b\u3055\u3044
+#thumbnail
+mark.thumbnail=\u30b5\u30e0\u30cd\u30a4\u30eb
+mark.thumbnail.open=\u30aa\u30fc\u30d7\u30f3
+mark.thumbnail.close=\u30af\u30ed\u30fc\u30ba
+#view-sidebar
+mark.sidebar=\u30b5\u30a4\u30c9\u30d0\u30fc
+#mark-history
+mark.history.title=\u30ec\u30d3\u30e5\u30fc
+mark.history.time=\u6642\u9593
+mark.history.number=\u756a\u53f7
+mark.history.score=\u7dcf\u5f97\u70b9
+mark.history.search=\u8a66\u9a13\u7528\u7d19\u306e\u691c\u7d22
+mark.history.pre=\u524d\u3078
+mark.history.success=\u30ec\u30d3\u30e5\u30fc\u6210\u529f\uff0c\u7dcf\u5f97\u70b9\uff1a
+mark.history.problem=\u30ec\u30d3\u30e5\u30fc\u6210\u529f\uff0c\u554f\u984c\u7528\u7d19\u306e\u63d0\u51fa\u304c\u6210\u529f\u3057\u305f
+mark.history.loading=\u30ed\u30fc\u30c7\u30a3\u30f3\u30b0
+mark.history.error=\u63a1\u70b9\u306e\u8a18\u9332\u306f\u3057\u3070\u3089\u304f\u8aad\u3080\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\uff0c\u5f8c\u3067\u3084\u308a\u76f4\u3057\u3066\u4e0b\u3055\u3044
+mark.history.number.error=\u6570\u5b57\u3092\u5165\u529b\u3057\u3066\u4e0b\u3055\u3044
+#mark-board
+mark.board.submit=\u63d0\u51fa
+mark.board.total.score=\u7dcf\u5f97\u70b9
+mark.board.score.zero=\u7dcf\u5f97\u70b9\u306f\uff10\u306b\u3059\u308b
+mark.board.pass=\u30b9\u30ad\u30c3\u30d7 
+mark.board.choose.result=\u9078\u629e\u7d50\u679c
+mark.board.keyboard=\u30ad\u30fc\u30dc\u30fc\u30c9\u3067\u63a1\u70b9 >>
+mark.board.mouse=<< \u30de\u30a6\u30b9\u3067\u63a1\u70b9
+mark.board.interval=\u9593\u9694
+mark.board.score=\u30b9\u30b3\u30a2
+mark.board.back=\u30d0\u30c3\u30af
+mark.board.clear.question=\u5f53\u9762\u306e\u984c\u76ee\u306e\u5f97\u70b9\u3092\u30af\u30fc\u30ea\u30a2\u3059\u308b
+mark.board.clear.question=\u7dcf\u5f97\u70b9\u3092\u30af\u30fc\u30ea\u30a2\u3059\u308b
+mark.board.question.not.mark=\u5f53\u9762\u306e\u30bf\u30b9\u30af\u306f\u307e\u3060\u63a1\u70b9\u3057\u3066\u3044\u306a\u3044\u984c\u76ee\u304c\u3042\u308b\u306e\u3067\uff0c\u63a1\u70b9\u3057\u3066\u4e0b\u3055\u3044\u3002
+mark.board.confirm=,\u63d0\u51fa\u3059\u308b\u304b\uff1f
+mark.board.zero.confirm=\u7dcf\u5f97\u70b9\u306f\uff10\u3067\u63d0\u51fa\u3059\u308b\u304b\uff1f
+mark.board.null=
+mark.board.interval.error=\u9593\u9694\u30b9\u30b3\u30a2\u306b\u5408\u308f\u306a\u3044
+mark.board.gt=\u5927\u306a\u308a
+mark.board.lt=\u5c0f\u306a\u308a
+mark.board.number.error=\u6570\u5b57\u3067\u306f\u3042\u308a\u307e\u305b\u3093
+#sheet-view
+mark.sheet=\u539f\u56f3
+mark.sheet.check=\u539f\u56f3\u306e\u5207\u308a\u66ff\u3048
+mark.sheet.open=\u30aa\u30fc\u30d7\u30f3
+mark.sheet.close=\u30af\u30ed\u30fc\u30ba
+#answer-view
+mark.answer=\u6b63\u89e3
+#warning-info
+mark.warning.try.again=\u3084\u308a\u76f4\u3057\u30dc\u30bf\u30f3\u3092\u30af\u30ea\u30c3\u30af\u3057\u3066\u4e0b\u3055\u3044\uff01
+mark.warning.force.special.tag=\u7279\u6b8a\u306a\u76ee\u5370\u306e\u5f37\u5236\u6a5f\u80fd\u304c\u4f7f\u7528\u3057\u305f\uff0c\u4e00\u3064\u4ee5\u4e0a\u306e\u7279\u6b8a\u306a\u76ee\u5370\u3092\u4f7f\u7528\u3057\u3066\u4e0b\u3055\u3044
+mark.warning.network.error=\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u7570\u5e38\uff0c\u30bf\u30b9\u30af\u306e\u63d0\u51fa\u304c\u5931\u8d25\u3057\u305f
+mark.warning.task.error=\u30bf\u30b9\u30af\u306e\u53d6\u5f97\u304c\u5931\u6557\u3057\u305f
+mark.warning.task.finish=\u3059\u3079\u3066\u306e\u30bf\u30b9\u30af\u306f\u5b8c\u6210\u3057\u305f
+mark.warning.task.loading=\u30bf\u30b9\u30af\u30ed\u30fc\u30c7\u30a3\u30f3\u30b0
+mark.warning.close=\u30af\u30ed\u30fc\u30ba
+mark.warning.success=\u30ec\u30d3\u30e5\u30fc\u6210\u529f\u3001\u7dcf\u5f97\u70b9\uff1a
+mark.warning.problem=\u30ec\u30d3\u30e5\u30fc\u554f\u984c\u7528\u7d19\u6210\u529f
+#json-view
+mark.json.loading=\u30ed\u30fc\u30c7\u30a3\u30f3\u30b0
+mark.json.student.answer=\u53d7\u9a13\u751f\u306e\u7b54\u3048\uff1a
+mark.json.answer=\u6b63\u89e3\uff1a
+mark.json.body=\u984c\u76ee\uff1a
+mark.json.question.number=\u984c\u76ee\u306e\u756a\u53f7\uff1a

+ 138 - 0
stmms-web/src/main/webapp/static/i18n/messages_zh.properties

@@ -0,0 +1,138 @@
+#login
+user.login.title=\u9ad8\u6821\u8003\u8bd5\u7ba1\u7406\u5e73\u53f0
+user.login.admin=\u7ba1\u7406\u5458\u767b\u5f55
+user.login.marker=\u8bc4\u5377\u5458\u767b\u5f55
+user.login.name=\u7528\u6237\u540d
+user.login.password=\u5bc6\u7801
+user.login.submit=\u767b\u5f55
+user.login.error.account=\u5e10\u53f7\u4e0d\u5b58\u5728
+user.login.error.finish=\u8bc4\u5377\u5df2\u7ed3\u675f
+user.login.error.group=\u5927\u9898\u4e0d\u5b58\u5728
+user.login.error.disabled=\u5e10\u53f7\u5df2\u7981\u7528
+user.login.error.password=\u5bc6\u7801\u9519\u8bef
+user.login.error.access=\u7528\u6237\u6ca1\u6709\u8bbf\u95ee\u6743\u9650
+#reset
+user.reset.submit=\u786e\u5b9a
+user.reset.logout=\u9000\u51fa
+user.reset.title=\u9996\u6b21\u767b\u5f55\uff0c\u8bf7\u5b8c\u5584\u8d44\u6599
+user.reset.name=\u8f93\u5165\u7528\u6237\u540d
+user.reset.password=\u8f93\u5165\u65b0\u5bc6\u7801
+user.reset.password.again=\u518d\u6b21\u8f93\u5165\u65b0\u5bc6\u7801
+user.reset.name.length=\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc710\u4e2a\u5b57
+user.reset.password.same=\u4e24\u6b21\u5bc6\u7801\u8bf7\u4fdd\u6301\u4e00\u81f4
+user.reset.password.length=\u5bc6\u7801\u7684\u957f\u5ea6\u81f3\u5c114\u4f4d\uff0c\u4e0d\u80fd\u8d85\u8fc78\u4f4d
+#mark-control
+mark.control.assistant=\u5c0f\u52a9\u624b
+mark.control.mode.track=\u5207\u6362\u5230\u8f68\u8ff9\u6a21\u5f0f
+mark.control.mode.common=\u5207\u6362\u5230\u666e\u901a\u6a21\u5f0f
+mark.control.function=\u8bc4\u5377\u529f\u80fd
+mark.control.logout=\u9000\u51fa
+mark.control.init.error=\u521d\u59cb\u5316\u5931\u8d25\uff0c\u8bf7\u5237\u65b0\u9875\u9762\u91cd\u65b0\u52a0\u8f7d
+mark.control.task.not.exist=\u8bc4\u5377\u5927\u9898\u4e0d\u5b58\u5728
+mark.control.task.finish=\u8bc4\u5377\u5df2\u7ed3\u675f
+mark.control.task.null=\u5f53\u524d\u65e0\u8bc4\u5377\u4efb\u52a1
+mark.control.tsak.error=\u8bc4\u5377\u4efb\u52a1\u63d0\u4ea4\u5931\u8d25\uff0c\u8bf7\u5237\u65b0\u9875\u9762
+#status
+mark.status.student.number=\u8003\u751f\u7f16\u53f7
+mark.status.library.number=\u4efb\u52a1\u7f16\u53f7
+mark.status.objective.score=\u5ba2\u89c2\u5f97\u5206
+mark.status.marked.count=\u5df2\u8bc4
+mark.status.unmark.count=\u672a\u8bc4
+mark.status.top.count=\u5206\u914d
+mark.status.progress=\u8fdb\u5ea6
+mark.status.continue=\u7ee7\u7eed
+mark.status.top.count.finish=\u5206\u914d\u4efb\u52a1\u5df2\u8bc4\u5b8c\uff0c\u662f\u5426\u7ee7\u7eed\uff1f
+mark.status.loading=\u6b63\u5728\u52a0\u8f7d...
+mark.status.logout=\u9000\u51fa
+#single-image-view
+mark.single.zoom.in=\u653e\u5927
+mark.single.zoom.out=\u7f29\u5c0f
+mark.single.zoom.fit=\u9002\u5e94
+mark.single.student.answer=\u7b54\u5377
+#chang name
+mark.change.name=\u4fee\u6539\u4e2a\u4eba\u4fe1\u606f
+mark.change.press.name=\u8f93\u5165\u7528\u6237\u540d
+mark.change.press.password=\u8f93\u5165\u65b0\u5bc6\u7801
+mark.change.press.password.again=\u518d\u6b21\u8f93\u5165\u65b0\u5bc6\u7801
+mark.change.confirm=\u786e\u5b9a
+mark.change.name.null=\u540d\u5b57\u4e0d\u80fd\u4e3a\u7a7a
+mark.change.name.length=\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc78\u4e2a\u5b57
+mark.change.password.same=\u4e24\u6b21\u5bc6\u7801\u8bf7\u4fdd\u6301\u4e00\u81f4
+mark.change.password.length=\u5bc6\u7801\u7684\u957f\u5ea6\u81f3\u5c114\u4f4d
+mark.change.network.error=\u7f51\u7edc\u901a\u4fe1\u9519\u8bef\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5
+mark.change.error=\u4fee\u6539\u5931\u8d25\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5
+#specialTag
+mark.special.tag=\u7279\u6b8a\u6807\u8bb0
+mark.special.back=\u56de\u9000
+mark.special.clear=\u5168\u90e8\u6e05\u9664
+mark.special.underline=\u4e0b\u5212\u7ebf
+mark.special.open=\u6253\u5f00
+mark.special.close=\u5173\u95ed
+#problem-process
+mark.problem=\u95ee\u9898\u5377
+mark.problem.type=\u9009\u62e9\u95ee\u9898\u7c7b\u578b
+mark.problem.confirm=\u786e\u5b9a
+mark.problem.cancel=\u53d6\u6d88
+mark.problem.check=\u8bf7\u9009\u62e9\u95ee\u9898\u5206\u7c7b
+#thumbnail
+mark.thumbnail=\u7f29\u7565\u56fe
+mark.thumbnail.open=\u6253\u5f00
+mark.thumbnail.close=\u5173\u95ed
+#view-sidebar
+mark.sidebar=\u4fa7\u8fb9\u680f
+#mark-history
+mark.history.title=\u56de\u8bc4
+mark.history.time=\u65f6\u95f4
+mark.history.number=\u7f16\u53f7
+mark.history.score=\u603b\u5206
+mark.history.search=\u67e5\u627e\u8bd5\u5377
+mark.history.pre=\u524d
+mark.history.success=\u56de\u8bc4\u6210\u529f\uff0c\u603b\u5206\uff1a
+mark.history.problem=\u56de\u8bc4\u6210\u529f\uff0c\u5df2\u63d0\u4ea4\u95ee\u9898\u5377
+mark.history.loading=\u6b63\u5728\u52a0\u8f7d\u8bf7\u7a0d\u5019
+mark.history.error=\u6682\u65f6\u65e0\u6cd5\u8bfb\u53d6\u8bc4\u5377\u5386\u53f2\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5
+mark.history.number.error=\u8bf7\u8f93\u5165\u6570\u5b57
+#mark-board
+mark.board.submit=\u63d0\u4ea4
+mark.board.total.score=\u603b\u5206
+mark.board.score.zero=\u5168\u96f6\u5206
+mark.board.pass=\u8df3 \u8fc7
+mark.board.choose.result=\u9009\u505a\u7ed3\u679c
+mark.board.keyboard=\u952e\u76d8\u7ed9\u5206 >>
+mark.board.mouse=<< \u9f20\u6807\u7ed9\u5206
+mark.board.interval=\u95f4\u9694
+mark.board.score=\u5206
+mark.board.back=\u56de\u9000
+mark.board.clear.question=\u6e05\u9664\u672c\u9898
+mark.board.clear.question=\u6e05\u9664\u5168\u5377
+mark.board.question.not.mark=\u5f53\u524d\u4efb\u52a1\u8fd8\u6709\u672a\u7ed9\u5206\u7684\u9898\uff0c\u8bf7\u7ee7\u7eed\u7ed9\u5206
+mark.board.confirm=,\u786e\u8ba4\u63d0\u4ea4\u5417\uff1f
+mark.board.zero.confirm=\u786e\u8ba4\u8981\u63d0\u4ea4\u5168\u96f6\u5206\u5417\uff1f
+mark.board.null=\u65e0
+mark.board.interval.error=\u4e0d\u7b26\u5408\u5206\u503c\u95f4\u9694\u8981\u6c42
+mark.board.gt=\u4e0d\u80fd\u5927\u4e8e
+mark.board.lt=\u4e0d\u80fd\u5c0f\u4e8e
+mark.board.number.error=\u4e0d\u662f\u5408\u6cd5\u6570\u5b57
+#sheet-view
+mark.sheet=\u539f\u56fe
+mark.sheet.check=\u539f\u56fe\u5207\u6362
+mark.sheet.open=\u6253\u5f00
+mark.sheet.close=\u5173\u95ed
+#answer-view
+mark.answer=\u6807\u7b54
+#warning-info
+mark.warning.try.again=\u8bf7\u70b9\u51fb\u91cd\u8bd5
+mark.warning.force.special.tag=\u5f3a\u5236\u7279\u6b8a\u6807\u8bb0\u5df2\u5f00\u542f\uff0c\u81f3\u5c11\u4f7f\u7528\u4e00\u4e2a\u7279\u6b8a\u6807\u8bb0
+mark.warning.network.error=\u7f51\u7edc\u5f02\u5e38\uff0c\u4efb\u52a1\u63d0\u4ea4\u5931\u8d25
+mark.warning.task.error=\u9886\u53d6\u8bc4\u5377\u4efb\u52a1\u51fa\u9519
+mark.warning.task.finish=\u8bc4\u5377\u4efb\u52a1\u5df2\u5b8c\u6210
+mark.warning.task.loading=\u8bc4\u5377\u4efb\u52a1\u6b63\u5728\u52a0\u8f7d\u4e2d
+mark.warning.close=\u5173\u95ed
+mark.warning.success=\u56de\u8bc4\u6210\u529f\uff0c\u603b\u5206\uff1a
+mark.warning.problem=\u56de\u8bc4\u95ee\u9898\u5377\u6210\u529f
+#json-view
+mark.json.loading=\u6b63\u5728\u52a0\u8f7d\u4e2d
+mark.json.student.answer=\u8003\u751f\u7b54\u6848\uff1a
+mark.json.answer=\u6807\u7b54\uff1a
+mark.json.body=\u9898\u5e72\uff1a
+mark.json.question.number=\u9898\u53f7\uff1a

+ 35 - 0
stmms-web/src/main/webapp/static/mark-json/js/json-loader.js

@@ -0,0 +1,35 @@
+//多媒体显示模块
+var json_loader = function (option, success) {
+    var object = new JsonLoader(option);
+    success();
+    return object;
+}
+
+function JsonLoader(option) {
+    this.markControl = option.markControl;
+    this.jsonServer = option.jsonServer;
+}
+
+JsonLoader.prototype.build = function (task, callback) {
+    var self = this;
+    if (task != undefined && task.jsonUrl != undefined) {
+//      TODO-测试代码,读取固定的json文件
+//         var result = '[{"mainNumber": 2,"subNumber": 10,"body": {"sections": [{ "blocks":[{"type": "text","value": "我是题目我是题目我是题目"}]}]},"answer": {"sections": [{"blocks":[{"type": "image","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/20/3_8_20_15748452889591137.jpeg"}]},{"blocks":[{"type": "audio","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/19/3_8_19_15748452552644264.mp3"}]}]},"studentAnswer": {"sections": [{"blocks":[{"type": "image","value": "https://ecs-test-static.qmth.com.cn/oe-answer-file/3/8/20/3_8_20_15748452889591137.jpeg"},{"type": "image","value": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1582709869784&di=39683b1330bd09bebd93c95fe925ca70&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn10108%2F170%2Fw600h370%2F20190222%2Fd6de-htknpmh2595255.jpg"},{"type": "text","value": "我是答案答案daan"}]}]}}]';
+//         task.jsonData = JSON.parse(result);
+//         callback();
+         var url = this.jsonServer + task.jsonUrl;
+         $.ajax({  
+             type:"GET",  
+             url:url,  
+             dataType:"json",  
+             success:function(data){
+	             task.jsonData = data;
+	             callback();
+             },
+             error:function () {
+                 console.log('json load error:' + url);
+                 callback('json load error');
+             }
+         });
+    }
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio