deason 5 anos atrás
pai
commit
8fee83a112
100 arquivos alterados com 10332 adições e 2 exclusões
  1. 20 0
      .gitignore
  2. 0 2
      README.md
  3. 22 0
      examcloud-core-questions-api-provider/pom.xml
  4. 98 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/AudioTimeConfigController.java
  5. 168 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CourseController.java
  6. 175 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CoursePropertyController.java
  7. 91 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DefaultPaperController.java
  8. 119 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DefaultQuesionController.java
  9. 90 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DemoController.java
  10. 36 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExamFileController.java
  11. 149 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExportPaperController.java
  12. 79 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExportStructureController.java
  13. 143 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExportTemplateController.java
  14. 286 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExtractConfigController.java
  15. 191 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/GenPaperController.java
  16. 139 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ImportPaperController.java
  17. 148 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/IndexController.java
  18. 34 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/InitQuesHashController.java
  19. 727 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperController.java
  20. 115 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperDetailController.java
  21. 91 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperDetailUnitController.java
  22. 152 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperStructController.java
  23. 132 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PropertyController.java
  24. 177 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/QuesController.java
  25. 55 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/QuestionAudioController.java
  26. 75 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/SettingController.java
  27. 173 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/ExtractConfigCloudServiceProvider.java
  28. 233 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/HandleSyncCloudServiceProvider.java
  29. 73 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/PaperCloudServiceProvider.java
  30. 95 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/QuestionCloudServiceProvider.java
  31. 17 0
      examcloud-core-questions-api-provider/src/test/java/org/examcloud/core/questions/api/provider/AppTest.java
  32. 119 0
      examcloud-core-questions-base/pom.xml
  33. 125 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/BeanCopierUtil.java
  34. 67 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/CombinationUtils.java
  35. 847 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/CommonUtils.java
  36. 28 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/Constants.java
  37. 369 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/FileDisposeUtil.java
  38. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/GridFSUtil.java
  39. 11 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/IdUtils.java
  40. 143 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/ImageUtils.java
  41. 30 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/IoUtils.java
  42. 25 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/Model.java
  43. 58 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/SpringContextUtils.java
  44. 276 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/StringSimilarityUtils.java
  45. 74 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/ZipUtils.java
  46. 35 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/bean/ExportServiceManageBean.java
  47. 38 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/Constants.java
  48. 43 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/PolicyType.java
  49. 126 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/QuesStructType.java
  50. 88 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/Result.java
  51. 54 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PaperAnswerDto.java
  52. 93 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PaperDetailDto.java
  53. 97 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PaperDto.java
  54. 81 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PolicyDto.java
  55. 45 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PolicyWeightDto.java
  56. 189 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/QuestionDto.java
  57. 36 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/QuestionOptionDto.java
  58. 112 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/SimpleQuestionDto.java
  59. 35 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/AnswerVo.java
  60. 54 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/BlockVo.java
  61. 35 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/BodyVo.java
  62. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/OptionVo.java
  63. 68 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/PaperDetailVo.java
  64. 91 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/PaperVo.java
  65. 48 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/ParamVo.java
  66. 113 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/QuestionVo.java
  67. 35 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/SectionVo.java
  68. 225 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/Cryptogram.java
  69. 480 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/FileUtil.java
  70. 224 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/JsonMapper.java
  71. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/NumUtil.java
  72. 71 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/CourseSpeciatly.java
  73. 92 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/CourseSync.java
  74. 70 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/ExamCourseDto.java
  75. 57 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/enums/CourseLevel.java
  76. 82 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/dto/ExportTempDataDto.java
  77. 139 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/dto/ExportTemplateDto.java
  78. 41 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/em/enums/ExamType.java
  79. 19 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/AudioPositionType.java
  80. 53 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExamFileType.java
  81. 62 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExportTemplateType.java
  82. 32 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExportType.java
  83. 28 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExportWay.java
  84. 23 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExtractPolicy.java
  85. 28 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/GenPaperExpression.java
  86. 24 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/GenPaperFunction.java
  87. 30 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/GenPaperType.java
  88. 32 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperStatus.java
  89. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperStructType.java
  90. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperType.java
  91. 41 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PropertyDifficulty.java
  92. 32 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/QuesUnit.java
  93. 23 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/RandomGenPaperPolicy.java
  94. 31 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/Switch.java
  95. 57 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ColumnSetting.java
  96. 49 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelError.java
  97. 33 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelProperty.java
  98. 165 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelReader.java
  99. 7 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelReaderHandle.java
  100. 71 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelUtils.java

+ 20 - 0
.gitignore

@@ -0,0 +1,20 @@
+*.class
+
+# Proguard folder generated by ide
+.project
+.classpath
+.settings
+target/
+.idea/
+*.iml
+
+# Log Files
+*.log
+*.class
+
+
+# Package Files #
+*.jar
+*.war
+*.ear
+/cqb-paper/src/main/java/com/qmth/cqb/paper/service/.gitignore

+ 0 - 2
README.md

@@ -1,2 +0,0 @@
-# examcloud-core-questions
-

+ 22 - 0
examcloud-core-questions-api-provider/pom.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>examcloud-core-questions-api-provider</artifactId>
+    <packaging>jar</packaging>
+
+    <parent>
+        <artifactId>examcloud-core-questions</artifactId>
+        <groupId>cn.com.qmth.examcloud.core.questions</groupId>
+        <version>2019-SNAPSHOT</version>
+    </parent>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.core.questions</groupId>
+            <artifactId>examcloud-core-questions-service</artifactId>
+            <version>${examcloud.version}</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 98 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/AudioTimeConfigController.java

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.dao.entity.AudioTimeConfig;
+import cn.com.qmth.examcloud.core.questions.service.AudioTimeConfigService;
+import cn.com.qmth.examcloud.core.questions.service.cache.ExtractConfigPaperCache;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * @author chenken
+ * @date 2017年8月10日 下午3:31:42
+ * @company QMTH
+ * @description AudioTimeConfigController.java
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class AudioTimeConfigController extends ControllerSupport {
+    @Autowired
+    private ExtractConfigPaperCache extractConfigPaperCache;
+    @Autowired
+    private AudioTimeConfigService audioTimeConfigService;
+
+    @ApiOperation(value = "根据examId查询音频设置 ", notes = "根据examId查询音频设置")
+    @PostMapping(value = "/audioTimeConfig/{examId}/{courseCode}")
+    public ResponseEntity<Object> findAudioTimeConfig(@PathVariable String examId, @PathVariable String courseCode) {
+        List<AudioTimeConfig> audioTimeConfigs = audioTimeConfigService.findAudioTimeConfigByExamId(examId, courseCode);
+        return new ResponseEntity<>(audioTimeConfigs, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据传入的对象查询音频题", notes = "根据传入的对象查询音频题")
+    @PostMapping("/audioTimeConfig/all")
+    public ResponseEntity<Object> findAudioTimeConfigList(@RequestBody List<AudioTimeConfig> audioTimeConfigModelList) {
+        List<AudioTimeConfig> list = audioTimeConfigService.findAudioTimeConfigList(audioTimeConfigModelList);
+        return new ResponseEntity<>(list, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据传入的对象查询已经保存的音频题", notes = "根据传入的对象查询已经保存的音频题")
+    @PostMapping("/audioTimeConfig/update")
+    public ResponseEntity<Object> findUpdateList(@RequestBody List<AudioTimeConfig> audioTimeConfigModelList) {
+        List<AudioTimeConfig> list = audioTimeConfigService.findUpdateAudioTimeConfigList(audioTimeConfigModelList);
+        return new ResponseEntity<>(list, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "新增音频播放次数", notes = "新增音频播放次数")
+    @PostMapping("/addAudioTimeConfig")
+    public ResponseEntity<Object> addAudioTimeConfig(@RequestBody List<AudioTimeConfig> audioTimeConfigList, HttpServletRequest request) {
+        User user = getAccessUser();
+        if (user != null) {
+            audioTimeConfigService.saveAudioTimeConfig(audioTimeConfigList, user);
+            //清除缓存
+            for(AudioTimeConfig ep:audioTimeConfigList) {
+                Object[] keys=new Object[]{ep.getPaper().getId()};
+                Object[] subkeys=new Object[]{Long.valueOf(ep.getExamId()), ep.getCourseCode(),ep.getGroupCode()};
+                extractConfigPaperCache.refresh(keys,subkeys);
+            }
+            return new ResponseEntity<>(HttpStatus.OK);
+        }
+        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+    }
+
+    @ApiOperation(value = "清理音频播放次数设置", notes = "清理音频播放次数设置")
+    @PostMapping("/deleteAudioTimeConfig/{examId}/{courseCode}")
+    public ResponseEntity<Object> deleteAudioTimeConfig(@PathVariable String examId, @PathVariable String courseCode) {
+        User user = getAccessUser();
+        if (user != null) {
+            audioTimeConfigService.deleteByExamIdAndCourseCode(examId, courseCode);
+            return new ResponseEntity<>(HttpStatus.OK);
+        }
+        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
+    }
+
+    @ApiOperation(value = "根据examID,courseCode,groupCode,paperDetailUnit的ID查询音频次数", notes = "查询音频次数")
+    @GetMapping("/seachAudioTimeConfig/{examId}/{courseCode}/{groupCode}/{paperDetailUnitId}")
+    public ResponseEntity<Object> seachAudioTimeConfig(@PathVariable String examId,
+                                                       @PathVariable String courseCode,
+                                                       @PathVariable String groupCode,
+                                                       @PathVariable String paperDetailUnitId) {
+        Map<String, String> map = audioTimeConfigService.findAudioTimeConfig(examId, courseCode, groupCode, paperDetailUnitId);
+        return new ResponseEntity<>(map, HttpStatus.OK);
+    }
+}
+

+ 168 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CourseController.java

@@ -0,0 +1,168 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+/*package cn.com.qmth.examcloud.core.questions.api;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import cn.com.qmth.examcloud.commons.web.security.enums.RoleMeta;
+import com.google.gson.Gson;
+import cn.com.qmth.examcloud.core.questions.dao.CourseRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
+import cn.com.qmth.examcloud.core.questions.service.impl.CourseService;
+
+import io.swagger.annotations.ApiOperation;
+
+*//**
+ * Created by songyue on 16/12/26.
+ * <p>
+ * 获取全部课程
+ *
+ * @return 更新课程
+ * @param course
+ * @return 新增课程
+ * @param course
+ * @return 删除课程
+ * @param coruse_id
+ * @return 根据课程名称或者课程编号获取课程信息
+ * @param keyword
+ * @return 根据课程编号获取课程信息
+ * @param courseNo
+ * @return 查询所有课程
+ * @return 获取全部课程
+ * @return 更新课程
+ * @param course
+ * @return 新增课程
+ * @param course
+ * @return 删除课程
+ * @param coruse_id
+ * @return 根据课程名称或者课程编号获取课程信息
+ * @param keyword
+ * @return 根据课程编号获取课程信息
+ * @param courseNo
+ * @return 查询所有课程
+ * @return 获取全部课程
+ * @return 更新课程
+ * @param course
+ * @return 新增课程
+ * @param course
+ * @return 删除课程
+ * @param coruse_id
+ * @return 根据课程名称或者课程编号获取课程信息
+ * @param keyword
+ * @return 根据课程编号获取课程信息
+ * @param courseNo
+ * @return 查询所有课程
+ * @return
+ *//*
+
+@RestController
+@RequestMapping("${api_cqb}/")
+public class CourseController {
+
+    @Autowired
+    Gson gson;
+
+    @Autowired
+    CourseRepo courseRepo;
+
+    @Autowired
+    CourseService courseService;
+
+    *//**
+ * 获取全部课程
+ *
+ * @return
+ *//*
+    @ApiOperation(value = "获取全部课程", notes = "获取全部课程")
+    @GetMapping(value = "/course/{curPage}/{pageSize}")
+    public ResponseEntity getAllCourse(@ModelAttribute Course searchCondition, @PathVariable int curPage,
+            @PathVariable int pageSize) {
+        return new ResponseEntity(courseService.findAll(searchCondition, curPage, pageSize), HttpStatus.OK);
+    }
+
+    *//**
+ * 更新课程
+ *
+ * @param course
+ * @return
+ *//*
+    @ApiOperation(value = "更新课程", notes = "更新课程")
+    @PutMapping(value = "/course")
+    public ResponseEntity updateCourse(@ModelAttribute Course course) {
+        return new ResponseEntity(courseRepo.save(course), HttpStatus.OK);
+    }
+
+    *//**
+ * 新增课程
+ *
+ * @param course
+ * @return
+ *//*
+    @ApiOperation(value = "新增课程", notes = "新增课程")
+    @PostMapping(value = "/course")
+    public ResponseEntity addCourse(@ModelAttribute Course course) {
+        return new ResponseEntity(courseRepo.save(course), HttpStatus.OK);
+    }
+
+    *//**
+ * 删除课程
+ *
+ * @param coruse_id
+ * @return
+ *//*
+    @ApiOperation(value = "删除课程", notes = "删除课程")
+    @DeleteMapping(value = "/course/{coruse_id}")
+    public ResponseEntity removeCourse(@PathVariable String coruse_id) {
+        courseRepo.delete(coruse_id);
+        return new ResponseEntity(HttpStatus.OK);
+    }
+
+    *//**
+ * 根据课程名称或者课程编号获取课程信息
+ *
+ * @param keyword
+ * @return
+ *//*
+    @ApiOperation(value = "根据课程名称或者课程编号获取课程信息", notes = "根据课程名称或者课程编号获取课程信息")
+    @GetMapping(value = "/course")
+    public ResponseEntity getCourseByKeyword(@RequestParam String keyword) {
+        return new ResponseEntity(courseService.findCoursesByKeyword(keyword), HttpStatus.OK);
+    }
+
+    *//**
+ * 根据课程编号获取课程信息
+ *
+ * @param courseNo
+ * @return
+ *//*
+    @ApiOperation(value = "根据课程编号获取课程信息", notes = "根据课程编号获取课程信息")
+    @GetMapping(value = "/course/{courseNo}")
+    public ResponseEntity getCourseByNo(@PathVariable String courseNo) {
+        return new ResponseEntity(courseRepo.findFirstByCode(courseNo), HttpStatus.OK);
+    }
+
+    *//**
+ * 查询所有课程
+ *
+ * @return
+ *//*
+    @ApiOperation(value = " 查询所有课程", notes = "查询所有课程")
+    @GetMapping(value = "/course/allCourses")
+    public ResponseEntity getAllCourses() {
+        List<Course> courses = courseRepo.findAll();
+        return new ResponseEntity(courses, HttpStatus.OK);
+    }
+
+}
+*/

+ 175 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CoursePropertyController.java

@@ -0,0 +1,175 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+import org.springframework.data.domain.Sort.Direction;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.Constants;
+import cn.com.qmth.examcloud.core.questions.dao.entity.CourseProperty;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyDto;
+import cn.com.qmth.examcloud.core.questions.service.CoursePropertyService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * @author weiwenhai
+ * @describle 课程属性
+ * @date 2017.11.2
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class CoursePropertyController extends ControllerSupport {
+
+    @Autowired
+    private CoursePropertyService coursePropertyService;
+
+    @ApiOperation(value = "根据orgId查询所有课程属性")
+    @GetMapping(value = "/courseProperty/all")
+    public ResponseEntity<Object> findAllByOrg() {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<CourseProperty> courseProperties = coursePropertyService.findAllByOrgId(user.getRootOrgId());
+        return new ResponseEntity<>(courseProperties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据orgId查询所有课程属性(分页)")
+    @GetMapping(value = "/courseProperty/all/{curPage}/{pageSize}")
+    public ResponseEntity<Object> findAllByOrgId(@ModelAttribute CoursePropertyDto coursePropertyDto,
+            @PathVariable Integer curPage, @PathVariable Integer pageSize) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        coursePropertyDto.setOrgId(user.getRootOrgId());
+
+        PageRequest pageRequest = PageRequest.of(curPage - 1, pageSize, new Sort(Direction.DESC, "updateTime"));
+        Page<CourseProperty> coursePropertiesPage = coursePropertyService.findList(coursePropertyDto, pageRequest);
+
+        return new ResponseEntity<>(coursePropertiesPage, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "新增课程属性", notes = "新增课程属性")
+    @PostMapping(value = "/courseProperty/save")
+    public ResponseEntity<Object> saveCourseProperty(@RequestBody CoursePropertyDto coursePropertyDto) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        coursePropertyDto.setOrgId(user.getRootOrgId());
+        coursePropertyService.saveCourseProperty(coursePropertyDto);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "启用")
+    @PutMapping(value = "/courseProperty/open/{id}")
+    public ResponseEntity<Object> openCourseProperty(@PathVariable String id) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        coursePropertyService.updateCoursePropertyStatus(id, true);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "批量启用")
+    @PutMapping(value = "/courseProperty/opens/{ids}")
+    public ResponseEntity<Object> opensCourseProperty(@PathVariable String ids) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<String> idList = Stream.of(ids.split(",")).collect(Collectors.toList());
+        coursePropertyService.updateCoursePropertyStatus(idList, true);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "禁用")
+    @PutMapping(value = "/courseProperty/close/{id}")
+    public ResponseEntity<Object> closeCourseProperty(@PathVariable String id) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        coursePropertyService.updateCoursePropertyStatus(id, false);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "批量禁用")
+    @PutMapping(value = "/courseProperty/closes/{ids}")
+    public ResponseEntity<Object> closesCourseProperty(@PathVariable String ids) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<String> idList = Stream.of(ids.split(",")).collect(Collectors.toList());
+        coursePropertyService.updateCoursePropertyStatus(idList, false);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据courseId查询所有课程属性")
+    @GetMapping(value = "/courseProperty/all/{courseId}")
+    public ResponseEntity<Object> findAllByCourseId(@PathVariable Long courseId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<CourseProperty> courseProperties = coursePropertyService.findAllByCourseId(courseId);
+        return new ResponseEntity<>(courseProperties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据courseCode查询所有课程属性")
+    @GetMapping(value = "/courseProperty/code/{courseCode}")
+    public ResponseEntity<Object> findAllByCourseCode(@PathVariable String courseCode) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<CourseProperty> courseProperties = coursePropertyService.findAllByCourseCode(courseCode);
+        return new ResponseEntity<>(courseProperties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据courseCode查询所有开启的课程属性")
+    @GetMapping(value = "/courseProperty/enable/{courseCode}")
+    public ResponseEntity<Object> findAllEnable(@PathVariable String courseCode) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<CourseProperty> courseProperties = coursePropertyService.findByEnable(courseCode, true);
+        return new ResponseEntity<>(courseProperties, HttpStatus.OK);
+    }
+
+}

+ 91 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DefaultPaperController.java

@@ -0,0 +1,91 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.api.request.GetExtractConfigReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetPaperReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetQuestionListReq;
+import cn.com.qmth.examcloud.core.questions.service.PaperProviderService;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author weiwenhai
+ * @date 2018.10.8
+ * @company qmth
+ * @description controller
+ * @code 016
+ */
+@RestController
+@RequestMapping("${api_cqb}/default_paper")
+public class DefaultPaperController extends ControllerSupport {
+
+    @Autowired
+    PaperProviderService paperProviderService;
+
+    @ApiOperation(value = "外部接口组卷", notes = "外部接口组卷")
+    @PostMapping("/genPaper")
+    public ResponseEntity<Object> genPaper(@RequestBody GetQuestionListReq req) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException("Q-016038", "用户不存在!");
+        }
+        //组卷
+        String paperId = paperProviderService.genPaper(req.getQuestionIds(), req.getMap(), req.getName(), user.getRootOrgId().toString(), user.getDisplayName());
+        return new ResponseEntity<>(paperId, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "外部接口查询试卷", notes = "外部接口查询试卷")
+    @PostMapping("/findPaper")
+    public ResponseEntity<Object> findPaper(@RequestBody GetPaperReq req) {
+        String paperId = req.getPaperId();
+        if (StringUtils.isBlank(paperId)) {
+            throw new StatusException("Q-016057", "paperId is null");
+        }
+        DefaultPaper defaultPaper = paperProviderService.findPaper(paperId);
+        return new ResponseEntity<>(defaultPaper, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "外部接口查询试卷", notes = "外部接口查询试卷")
+    @PostMapping("/findExamPaper")
+    public ResponseEntity<Object> findExamPaper(@RequestBody GetExtractConfigReq req) {
+        String rootOrgId = req.getRootOrgId();
+        if (StringUtils.isBlank(rootOrgId)) {
+            throw new StatusException("Q-016068", "rootOrgId is null");
+        }
+        Long examId = req.getExamId();
+        if (null == examId) {
+            throw new StatusException("Q-016073", "examId is null");
+        }
+        DefaultPaper defaultPaper = paperProviderService.findExamPaper(rootOrgId, examId);
+        return new ResponseEntity<>(defaultPaper, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "外部接口组卷并绑定考卷", notes = "外部接口组卷并绑定考卷")
+    @PostMapping("/genAndExamPaper")
+    public ResponseEntity<Object> genPaperAndExamPaper(@RequestBody GetExtractConfigReq req) {
+        String rootOrgId = req.getRootOrgId();
+        if (StringUtils.isBlank(rootOrgId)) {
+            throw new StatusException("Q-016087", "rootOrgId is null");
+        }
+        Long examId = req.getExamId();
+        if (null == examId) {
+            throw new StatusException("Q-016091", "examId is null");
+        }
+        //组卷
+        String paperId = paperProviderService.genPaper(req.getQuestionIds(), req.getMap(), req.getPaperName(), req.getRootOrgId(), req.getUserName());
+        paperProviderService.examPaper(req.getRootOrgId(), paperId, req.getExamId());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+}

+ 119 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DefaultQuesionController.java

@@ -0,0 +1,119 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.api.request.GetDefaultQuesionReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetDefaultQuesionsReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetQuestionListReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetQuestionReq;
+import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
+import cn.com.qmth.examcloud.core.questions.service.QuestionProviderService;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestion;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author weiwenhai
+ * @date 20180.9.10
+ * @company qmth
+ * @description controller
+ * @code 013
+ */
+@RestController
+@RequestMapping("${api_cqb}/default_question")
+public class DefaultQuesionController extends ControllerSupport {
+
+    @Autowired
+    private QuestionProviderService questionProviderService;
+
+    @Autowired
+    private ExtractConfigProviderService extractConfigExamService;
+
+    @ApiOperation(value = "外部接口保存单个试题", notes = "外部接口保存单个试题")
+    @PostMapping("/save")
+    public ResponseEntity<Object> save(@RequestBody GetDefaultQuesionReq req) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException("Q-013041", "用户不存在!");
+        }
+        DefaultQuestion defaultQuestion = req.getDefaultQuestion();
+        if (defaultQuestion.getRootOrgId() == null) {
+            throw new StatusException("Q-013045", "rootOrgId is null");
+        }
+        String id = questionProviderService.save(defaultQuestion);
+        return new ResponseEntity<>(id, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "修改试题")
+    @PutMapping("/update")
+    public ResponseEntity<Object> update(@RequestBody GetDefaultQuesionReq req) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException("Q-013057", "用户不存在!");
+        }
+        DefaultQuestion defaultQuestion = req.getDefaultQuestion();
+        if (defaultQuestion.getRootOrgId() == null) {
+            throw new StatusException("Q-013061", "rootOrgId is null");
+        }
+        String id = questionProviderService.save(defaultQuestion);
+        return new ResponseEntity<>(id, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "查询试题")
+    @PostMapping("/find")
+    public ResponseEntity<Object> find(@RequestBody GetDefaultQuesionsReq req) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException("Q-013075", "用户不存在!");
+        }
+        Long rootOrgId = req.getRootOrgId();
+        if (rootOrgId == null) {
+            throw new StatusException("Q-011079", "rootOrgId is null");
+        }
+        Page<DefaultQuestion> defaultQuestions = questionProviderService.findQustions(rootOrgId, req.getProperties(), req.getCurPage(), req.getPageSize());
+        return new ResponseEntity<>(defaultQuestions, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "查询试题")
+    @PostMapping("/find_questions")
+    public ResponseEntity<Object> find(@RequestBody GetQuestionListReq req) {
+        Set<String> questionIds = req.getQuestionIds();
+        if (questionIds == null || questionIds.size() < 1) {
+            throw new StatusException("Q-011089", "questionId is null");
+        }
+        List<DefaultQuestion> defaultQuestions = questionProviderService.findQuestions(questionIds);
+        return new ResponseEntity<>(defaultQuestions, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "查询试题")
+    @PostMapping("/question")
+    public ResponseEntity<Object> findOne(@RequestBody GetQuestionReq questionReq) {
+        Long examId = questionReq.getExamId();
+        if (null == examId) {
+            throw new StatusException("Q-010078", "examId is null");
+        }
+        String courseCode = questionReq.getCourseCode();
+        if (StringUtils.isBlank(courseCode)) {
+            throw new StatusException("Q-010082", "courseCode is null");
+        }
+        String groupCode = questionReq.getGroupCode();
+        if (StringUtils.isBlank(groupCode)) {
+            throw new StatusException("Q-010086", "groupCode is null");
+        }
+        String questionId = questionReq.getQuestionId();
+        if (StringUtils.isBlank(questionId)) {
+            throw new StatusException("Q-010090", "questionId is null");
+        }
+        DefaultQuestion defaultQuestion = extractConfigExamService.getDefaultQuestion(examId, courseCode, groupCode, questionId);
+        return new ResponseEntity<>(defaultQuestion, HttpStatus.OK);
+    }
+}

+ 90 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DemoController.java

@@ -0,0 +1,90 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.core.questions.base.converter.utils.JsonMapper;
+import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
+import cn.com.qmth.examcloud.core.questions.service.bean.extract.ExtractConfigPaper;
+import cn.com.qmth.examcloud.core.questions.service.cache.*;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.*;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import cn.com.qmth.examcloud.web.support.Naked;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+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.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.Set;
+
+@Controller
+@RequestMapping("${api_cqb}/")
+public class DemoController {
+    private static final Logger log = LoggerFactory.getLogger(DemoController.class);
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+    @Autowired
+    private RedisClient redisClient;
+    @Autowired
+    private ExtractConfigCache extractConfigCache;
+    @Autowired
+    private ExtractConfigPaperCache extractConfigPaperCache;
+    @Autowired
+    private BasePaperCache basePaperCache;
+    @Autowired
+    private QuestionCache questionCache;
+    @Autowired
+    private QuestionAnswerCache questionAnswerCache;
+    @Autowired
+    private ExtractConfigProviderService extractConfigProviderService;
+
+    @Naked
+    @ResponseBody
+    @RequestMapping(value = "/demo", method = RequestMethod.GET)
+    public String demo(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        Long examId = 319L;
+        String courseCode = "course-fds";
+        String groupCode = "X";
+        String paperId = "f4e8b8a3-9f2f-4455-a3c1-62942f1ed171";
+        String questionId = "5d5e109aef8fce4eeda1ee12";
+
+        ExtractConfigPaper extractConfigPaper = extractConfigProviderService.getDefaultPaper(examId, courseCode, groupCode);
+        log.info("调卷的试卷结构缓存:" + extractConfigPaper.getDefaultPaper().getName());
+
+        ExtractConfigCacheBean extractConfigCacheBean = CacheHelper.getExtractConfig(examId, courseCode);
+        log.info("--->调卷规则缓存: " + extractConfigCacheBean.getDetails().size());
+        //extractConfigCache.remove(examId, courseCode);
+
+        ExtractConfigPaperCacheBean extractConfigPaperCacheBean = CacheHelper.getExtractConfigPaper(examId, courseCode, groupCode, paperId);
+        log.info("--->调卷规则的试卷结构缓存: " + extractConfigPaperCacheBean.getDefaultPaper().getName());
+        //extractConfigPaperCache.remove(examId, courseCode, groupCode, paperId);
+
+        BasePaperCacheBean basePaperCacheBean = CacheHelper.getBasePaper(paperId);
+        log.info("--->试卷结构缓存: " + basePaperCacheBean.getDefaultPaper().getName());
+        //basePaperCache.remove(paperId);
+
+        QuestionCacheBean questionCacheBean = CacheHelper.getQuestion(examId, courseCode, groupCode, questionId);
+        log.info("--->试题缓存: " + questionCacheBean.getDefaultQuestion().getId());
+        //questionCache.remove(examId, courseCode, groupCode, questionId);
+
+        QuestionAnswerCacheBean questionAnswer = CacheHelper.getQuestionAnswer("5d5e109aef8fce4eeda1ee22");
+        log.info("--->试题答案缓存: " + new JsonMapper().toJson(questionAnswer));
+        //questionAnswerCache.remove(questionId);
+
+        final String pKeys = Constants.CACHE_KEY_PAPER + "*" + paperId;
+        Set<String> paperKeys = redisTemplate.keys(pKeys);
+        log.info(StringUtils.join(paperKeys, ","));
+
+        final String qKeys = Constants.CACHE_KEY_QUESTION + "*" + questionId;
+        Set<String> questionKeys = redisTemplate.keys(qKeys);
+        log.info(StringUtils.join(questionKeys, ","));
+
+        return "ok";
+    }
+
+}

+ 36 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExamFileController.java

@@ -0,0 +1,36 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExamFile;
+import cn.com.qmth.examcloud.core.questions.service.ExamFileService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2017年7月25日 下午2:21:57
+ * @company QMTH
+ * @description ExamFileController.java
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class ExamFileController {
+
+    @Autowired
+    private ExamFileService examFileService;
+
+    @ApiOperation(value = "查询试卷文件", notes = "查询试卷文件")
+    @PostMapping(value = "/findExamFile")
+    public ResponseEntity<Object> findExamFile(@RequestBody ExamFile examFile) {
+        List<ExamFile> examFiles = examFileService.findExamFileListByExamFile(examFile);
+        return new ResponseEntity<>(examFiles, HttpStatus.OK);
+    }
+}
+

+ 149 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExportPaperController.java

@@ -0,0 +1,149 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.dao.ExportServiceManageRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExportServiceManage;
+import cn.com.qmth.examcloud.core.questions.service.ExportPaperService;
+import cn.com.qmth.examcloud.core.questions.service.export.SydxExportPaperService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * Created by songyue on 17/3/30.
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class ExportPaperController extends ControllerSupport {
+
+    protected static final Logger log = LoggerFactory.getLogger(ExportPaperController.class);
+
+    @Autowired
+    ExportServiceManageRepo exportServiceManageRepo;
+
+    @Autowired
+    SydxExportPaperService sydxExportPaperService;
+
+    @Autowired
+    private ExportPaperService exportPaperService;
+
+    /**
+     * 导出单个试卷
+     *
+     * @param id
+     * @return
+     */
+    @ApiOperation(value = "导出试卷", notes = "导出试卷")
+    @GetMapping(value = "/paper/export/{id}/{exportContentList}/{orgId}/{loginName}/{examType}")
+    public void getPaperById(HttpServletRequest request, HttpServletResponse response, @PathVariable String id,
+            @PathVariable String orgId, @PathVariable String exportContentList, @PathVariable String loginName,
+            @PathVariable String examType) {
+        log.info("导出开始");
+        String psw = request.getParameter("psw");
+        ExportServiceManage esm = exportServiceManageRepo.findByOrgId(orgId);
+
+        if (esm == null) {
+            throw new StatusException("500", "尚未配置导出功能!");
+        }
+        User user = getAccessUser();
+        exportPaperService.exportPaperFile(id, esm.getExportServiceName(), exportContentList, response, loginName,
+                examType, psw, user.getRootOrgId());
+        log.info("导出结束");
+    }
+
+    /**
+     * 批量导出试卷
+     *
+     * @param request
+     * @param response
+     * @param paperIds
+     * @param orgName
+     * @param exportContentList
+     * @param loginName
+     */
+    @ApiOperation(value = "批量导出试卷", notes = "批量导出")
+    @GetMapping(value = "/paper/batch_export/{paperIds}/{exportContentList}/{orgId}/{loginName}/{examType}")
+    public void getPaperByIds(HttpServletRequest request, HttpServletResponse response, @PathVariable String paperIds,
+            @PathVariable String orgId, @PathVariable String exportContentList, @PathVariable String loginName,
+            @PathVariable String examType) {
+        log.info("批量导出");
+        List<String> paperList = Stream.of(paperIds.split(",")).collect(Collectors.toList());
+        ExportServiceManage esm = exportServiceManageRepo.findByOrgId(orgId);
+
+        if (esm == null) {
+            throw new StatusException("500", "尚未配置导出功能!");
+        }
+
+        try {
+            User user = getAccessUser();
+            exportPaperService.exportPaperFiles(paperList, esm.getExportServiceName(), exportContentList, response,
+                    loginName, examType, user.getRootOrgId());
+        } catch (Exception e) {
+            log.error("导出异常:" + e.getMessage());
+        }
+        log.info("导出结束");
+    }
+
+    @ApiOperation(value = "导出试题分布数量(按课程)", notes = "导出试题分布数量(按课程)")
+    @GetMapping(value = "/paper/export/course/question/{courseNo}")
+    public void downQuestionDistribute(HttpServletResponse response, @PathVariable String courseNo) {
+        log.info("开始导出Excel");
+        try {
+            exportPaperService.downQuestionDistribute(courseNo, response);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "导出试题分布数量(按试卷)", notes = "导出试题分布数量(按试卷)")
+    @GetMapping(value = "/paper/export/course/question/papers/{paperIds}")
+    public void downQuestionDistributeByPapers(HttpServletResponse response, @PathVariable String paperIds) {
+        log.info("开始导出Excel");
+        try {
+            exportPaperService.downQuestionDistributeByPapers(paperIds, response);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "导出原始导入的试卷", notes = "导出原始导入的试卷")
+    @GetMapping(value = "/originalPaper/export/{paperId}/{loginName}")
+    public void downOriginalPaper(HttpServletResponse response, @PathVariable String paperId,
+            @PathVariable String loginName) {
+        try {
+            exportPaperService.downOriginalPaper(paperId, loginName, response);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        log.info("开始导出原始导入的试卷");
+    }
+
+    @ApiOperation(value = "导出原始导入的试卷", notes = "导出原始导入的试卷")
+    @GetMapping(value = "/originalPaper/exportPlus/{paperId}/{loginName}")
+    public void downOriginalPaperPlus(HttpServletResponse response, @PathVariable String paperId,
+            @PathVariable String loginName) {
+        try {
+            User user=getAccessUser();
+            exportPaperService.downOriginalPaperPlus(paperId, user.getRootOrgId(), response);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        log.info("开始导出原始导入的试卷");
+    }
+}

+ 79 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExportStructureController.java

@@ -0,0 +1,79 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExportStructure;
+import cn.com.qmth.examcloud.core.questions.service.ExportStructureService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author chenken
+ * @date 2017年7月12日 下午4:28:55
+ * @company QMTH
+ * @description ExportStructureController.java
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class ExportStructureController extends ControllerSupport {
+
+    @Autowired
+    private ExportStructureService exportStructureService;
+
+    @ApiOperation(value = "保存导出结构", notes = "保存导出结构")
+    @PostMapping(value = "/saveExportStructure")
+    public ResponseEntity<Object> saveExportStructure(@RequestBody ExportStructure exportStructure) {
+        try {
+            User user = getAccessUser();
+            exportStructureService.saveExportStructure(exportStructure, user);
+            return new ResponseEntity<>(HttpStatus.OK);
+        } catch (Exception e) {
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @ApiOperation(value = "分页查询导出结构", notes = "分页查询导出结构")
+    @PostMapping(value = "/findPageByExportStructure/{curPage}/{pageSize}")
+    public ResponseEntity<Object> findPageByExportStructure(@PathVariable int curPage, @PathVariable int pageSize,
+                                                            @RequestBody ExportStructure exportStructure) {
+        try {
+            User user = getAccessUser();
+            exportStructure.setOrgId(user.getRootOrgId() + "");
+            Page<ExportStructure> pageExportStructure = exportStructureService.findPageByExportStructure(exportStructure, curPage, pageSize);
+            return new ResponseEntity<>(pageExportStructure, HttpStatus.OK);
+        } catch (Exception e) {
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @ApiOperation(value = "根据考试ID查询导出结构", notes = "根据考试ID查询导出结构")
+    @GetMapping(value = "/findExportStructure/{examId}")
+    public ResponseEntity<Object> findExportStructure(@PathVariable String examId) {
+        User user = getAccessUser();
+        ExportStructure exportStructure = exportStructureService.findByExportStructure(user.getRootOrgId() + "", examId);
+        return new ResponseEntity<>(exportStructure, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据考试类型查询考试id", notes = "根据考试类型查询考试id")
+    @GetMapping(value = "/findExportStructureByExamType/{examType}")
+    public ResponseEntity<Object> findExportStructureByExamType(@PathVariable String examType) {
+        User user = getAccessUser();
+        List<String> list = exportStructureService.findExportStructureByExamType(examType, user);
+        return new ResponseEntity<>(list, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据导出结构id删除", notes = "根据导出结构删除")
+    @DeleteMapping(value = "/deleteExportStructure/{id}")
+    public ResponseEntity<Object> deleteExportStructure(@PathVariable String id) {
+        exportStructureService.deleteExportStructure(id);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}
+

+ 143 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExportTemplateController.java

@@ -0,0 +1,143 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import javax.validation.constraints.NotNull;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.bean.ExportServiceManageBean;
+import cn.com.qmth.examcloud.core.questions.base.dto.ExportTemplateDto;
+import cn.com.qmth.examcloud.core.questions.dao.ExportServiceManageRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExportServiceManage;
+import cn.com.qmth.examcloud.core.questions.service.ExportTemplateService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@Api(tags = "导出模板设定")
+@RestController
+@RequestMapping("${api_cqb}/exportTemplate")
+public class ExportTemplateController extends ControllerSupport {
+    @Autowired
+    ExportServiceManageRepo exportServiceManageRepo;
+    @Autowired
+    private ExportTemplateService exportTemplateService;
+
+    @ApiOperation(value = "分页查询")
+    @GetMapping("page/{pageNo}/{pageSize}")
+    public Page<ExportTemplateDto> getPage(@PathVariable Integer pageNo, @PathVariable Integer pageSize,
+            @RequestParam Long rootOrgId, @RequestParam(required = false) String fileName,
+            @RequestParam(required = false) String type) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100000", "无效的请求");
+            }
+        }
+        return exportTemplateService.getPage(rootOrgId, fileName, type, pageNo, pageSize);
+    }
+
+    @ApiOperation(value = "新增")
+    @PostMapping("add/{rootOrgId}")
+    public void addResource(@RequestParam @NotNull(message = "模板名称不能为空!") String templateName,
+            @PathVariable Long rootOrgId, @RequestParam @NotNull(message = "模板类型不能为空!") String type,
+            @RequestPart @NotNull(message = "上传文件不能为空!") MultipartFile dataFile) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100001", "无效的请求");
+            }
+        }
+        exportTemplateService.addFile(accessUser,rootOrgId, templateName, type, dataFile);
+    }
+
+    @ApiOperation(value = "删除")
+    @DeleteMapping("/{rootOrgId}/{id}")
+    public void delete(@PathVariable Long rootOrgId, @PathVariable Long id) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100002", "无效的请求");
+            }
+        }
+        exportTemplateService.delete(rootOrgId, id);
+    }
+
+    @ApiOperation(value = "启用")
+    @PutMapping("/enable/{rootOrgId}/{id}")
+    public void enable(@PathVariable Long rootOrgId, @PathVariable Long id) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100003", "无效的请求");
+            }
+        }
+        exportTemplateService.enable(rootOrgId, id);
+    }
+
+    @ApiOperation(value = "禁用")
+    @PutMapping("/disenable/{rootOrgId}/{id}")
+    public void disenable(@PathVariable Long rootOrgId, @PathVariable Long id) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100004", "无效的请求");
+            }
+        }
+        exportTemplateService.disenable(rootOrgId, id);
+    }
+    
+    @ApiOperation(value = "查询配置")
+    @GetMapping("/config/{rootOrgId}")
+    public ExportServiceManageBean getConfig(@PathVariable Long rootOrgId) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100005", "无效的请求");
+            }
+        }
+        ExportServiceManage esm = exportServiceManageRepo.findByOrgId(String.valueOf(rootOrgId));
+        ExportServiceManageBean bean=new ExportServiceManageBean();
+        if(esm!=null) {
+            bean.setOrgId(esm.getOrgId());
+            bean.setId(esm.getId());
+            bean.setExportServiceName(esm.getExportServiceName());
+        }
+        return bean;
+    }
+
+    @ApiOperation(value = "新增/修改配置")
+    @PutMapping("/config/{rootOrgId}")
+    public void modifyConfig(@RequestParam @NotNull(message = "配置名称不能为空!") String serviceName,
+            @PathVariable Long rootOrgId) {
+        User accessUser = getAccessUser();
+        if (!isSuperAdmin()) {
+            if (!rootOrgId.equals(accessUser.getRootOrgId())) {
+                throw new StatusException("100006", "无效的请求");
+            }
+        }
+        if(StringUtils.isBlank(serviceName)) {
+            throw new StatusException("100007", "配置名称不能为空!");
+        }
+        ExportServiceManage esm = exportServiceManageRepo.findByOrgId(String.valueOf(rootOrgId));
+        if(esm==null) {
+            esm=new ExportServiceManage();
+            esm.setOrgId(String.valueOf(rootOrgId));
+        }
+        esm.setExportServiceName(serviceName.trim());
+        exportServiceManageRepo.save(esm);
+    }
+}

+ 286 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExtractConfigController.java

@@ -0,0 +1,286 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.enums.ExportWay;
+import cn.com.qmth.examcloud.core.questions.base.question.PaperDto;
+import cn.com.qmth.examcloud.core.questions.base.question.QuestionDto;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExtractConfig;
+import cn.com.qmth.examcloud.core.questions.service.ExtractConfigFileService;
+import cn.com.qmth.examcloud.core.questions.service.ExtractConfigService;
+import cn.com.qmth.examcloud.core.questions.service.bean.CouresInfo;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.ExportPaperInfoModel;
+import cn.com.qmth.examcloud.core.questions.service.cache.ExtractConfigCache;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * @author chenken
+ * @date 2017年4月14日 下午6:05:37
+ * @company QMTH
+ * @description 调卷规则控制器
+ * @code 050
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class ExtractConfigController extends ControllerSupport {
+    private static final Logger logger = LoggerFactory.getLogger(ExtractConfigController.class);
+
+    @Autowired
+    private ExtractConfigService extractConfigService;
+
+    @Autowired
+    private ExtractConfigFileService extractConfigFileService;
+
+    @Autowired
+    private ExtractConfigCache extractConfigCache;
+
+    @ApiOperation(value = "根据考试ID和课程ID获取调卷规则", notes = "根据考试ID和课程ID获取调卷规则")
+    @GetMapping(value = "/findPageExtractConfig/{currentPage}/{pageSize}")
+    public ResponseEntity<Object> findPageExtractConfig(@PathVariable int currentPage,
+                                                        @PathVariable int pageSize,
+                                                        @RequestParam("examId") Long examId,
+                                                        @RequestParam("courseNo") String courseNo) {
+        try {
+            User user = getAccessUser();
+            Page<ExtractConfig> extractConfigPageList = extractConfigService.findPageExtractConfig(currentPage, pageSize, examId, courseNo, String.valueOf(user.getRootOrgId()));
+            return new ResponseEntity<>(extractConfigPageList, HttpStatus.OK);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("Q-050065", e.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "保存调卷规则", notes = "保存调卷规则")
+    @PutMapping(value = "/extractConfig/{isBuildFile}")
+    public ResponseEntity<Object> saveExtractConfig(@PathVariable Integer isBuildFile, @RequestBody ExtractConfig extractConfig) {
+        try {
+            User user = getAccessUser();
+            extractConfig.setOrgId(user.getRootOrgId() + "");
+            extractConfig.setOrgName(user.getRootOrgName());
+            extractConfigFileService.saveExtractConfigAndBuildPaperFile(extractConfig, isBuildFile, user);
+
+            //清除缓存
+            extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
+
+            return new ResponseEntity<>(HttpStatus.OK);
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            throw new StatusException("Q-050099", e.getMessage());
+        }
+    }
+
+    @ApiOperation(value = "根据ID获取调卷规则", notes = "根据ID获取调卷规则")
+    @GetMapping(value = "/extractConfig/{id}")
+    public ResponseEntity<ExtractConfig> findExtractConfigById(@PathVariable String id) {
+        ExtractConfig extractConfig = extractConfigService.findConfigById(id);
+        return new ResponseEntity<ExtractConfig>(extractConfig, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据考试Id获取已经制定的调卷规则", notes = "根据考试Id获取已经制定的调卷规则")
+    @GetMapping(value = "/findCourseByExtractConfig/{examId}")
+    public ResponseEntity<Object> findCourseByExtractConfig(@PathVariable Long examId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException("Q-", "user is null");
+        }
+        List<CouresInfo> courseCodeList = extractConfigService.findCourseByExtractConfig(examId, user.getRootOrgId().toString());
+        return new ResponseEntity<>(courseCodeList, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据考试ID和课程ID获取调卷规则", notes = "根据考试ID和课程ID获取调卷规则")
+    @GetMapping(value = "/extractConfig/{examId}/{courseCode}")
+    public ResponseEntity<ExtractConfig> findExtractConfig(@PathVariable Long examId, @PathVariable String courseCode) {
+        ExtractConfig condition = new ExtractConfig();
+        condition.setExamId(examId);
+        condition.setCourseCode(courseCode);
+        ExtractConfig extractConfig = extractConfigService.findConfig(condition);
+        return new ResponseEntity<ExtractConfig>(extractConfig, HttpStatus.OK);
+    }
+
+    @Naked
+    @ApiOperation(value = "抽取考试试卷", notes = "抽取考试试卷")
+    @GetMapping(value = "/extract/{exam_id}/{course_code}/{group_code}")
+    public ResponseEntity<Object> extract(@PathVariable Long exam_id, @PathVariable String course_code, @PathVariable String group_code) {
+        try {
+            Map<String, Object> returnMap = extractConfigService.extractExamPaper(exam_id, course_code, group_code);
+            if (returnMap.get("errorMsg") == null) {
+                PaperDto paperDto = (PaperDto) returnMap.get("paperDto");
+                return new ResponseEntity<>(paperDto, HttpStatus.OK);
+            } else {
+                return new ResponseEntity<>(returnMap.get("errorMsg") + "", HttpStatus.OK);
+            }
+        } catch (Exception e) {
+            logger.error("抽卷失败", e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "抽取单个试题", notes = "抽取单个试题")
+    @GetMapping(value = "/extractQues/{examId}/{courseCode}/{groupCode}/{paperDetailUnitId}")
+    public ResponseEntity<Object> extractQuestion(@PathVariable String examId,
+                                                  @PathVariable String courseCode,
+                                                  @PathVariable String groupCode,
+                                                  @PathVariable String paperDetailUnitId) {
+        try {
+            QuestionDto questionDto = extractConfigService.extractExamQuestion(examId, courseCode, groupCode, paperDetailUnitId);
+            return new ResponseEntity<>(questionDto, HttpStatus.OK);
+        } catch (Exception e) {
+            logger.error("抽题失败", e);
+            return new ResponseEntity<>("抽题失败:" + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+
+    }
+
+    @Naked
+    @ApiOperation(value = "判断试卷中的题是否都为客观题(单选、多选、判断),包括套题中的小题",
+            notes = "判断试卷中的题是否都为客观题(单选、多选、判断),包括套题中的小题")
+    @GetMapping(value = "/checkObjective/{paperId}")
+    public ResponseEntity<Map<String, Object>> checkIsAllObjectiveQuestion(@PathVariable String paperId) {
+        Map<String, Object> quesMap = new HashMap<>();
+        try {
+            if (StringUtils.isBlank(paperId)) {
+                quesMap.put("message", "paperId不能为空");
+                return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+            }
+            boolean result = extractConfigService.checkIsAllQbjectiveQuestion(paperId);
+            quesMap.put("result", result);
+            quesMap.put("message", result ? "全为客观题" : "不全为客观题");
+            return new ResponseEntity<>(quesMap, HttpStatus.OK);
+        } catch (Exception e) {
+            logger.error("调用checkIsAllQbjectiveQuestion失败", e);
+            quesMap.put("result", "error");
+            quesMap.put("message", "调用失败");
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "导出单张考试试卷、答案、试卷结构", notes = "导出单张考试试卷、答案、试卷结构")
+    @GetMapping(value = "/exportSingleExamPaperInfo/{orgId}/{exportWay}/{examId}/{courseId}/{exportContentList}/{loginName}")
+    public void exportSingleExamPaperInfo(HttpServletResponse response,
+                                          @PathVariable String exportWay,
+                                          @PathVariable String orgId,
+                                          @PathVariable String examId,
+                                          @PathVariable String courseId,
+                                          @PathVariable String exportContentList,
+                                          @PathVariable String loginName,
+                                          @RequestParam(required = false) String psw) {
+        ExportPaperInfoModel exportModel = new ExportPaperInfoModel();
+        exportModel.setExportWay(ExportWay.strToEnum(exportWay));
+        exportModel.setExamId(examId);
+        exportModel.setCourseId(courseId);
+        String[] exportContentArray = exportContentList.split(",");
+        List<String> list = new ArrayList<>();
+        for (int i = 0; i < exportContentArray.length; i++) {
+            list.add(exportContentArray[i]);
+        }
+        exportModel.setExportContentList(list);
+        try {
+            extractConfigFileService.exportExamPaperInfo(exportModel, response, loginName, orgId, psw);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    @ApiOperation(value = "导出试卷文件前校验", notes = "导出试卷文件前校验")
+    @GetMapping(value = "/exportBatchExamPaperInfoCheck/{exportWay}/{examId}")
+    public ResponseEntity<Object> exportExamPaperInfoCheck(HttpServletResponse response,
+                                                           @PathVariable String exportWay,
+                                                           @PathVariable String examId) {
+        ExportPaperInfoModel exportModel = new ExportPaperInfoModel();
+        exportModel.setExportWay(ExportWay.strToEnum(exportWay));
+        exportModel.setExamId(examId);
+        try {
+            extractConfigFileService.exportExamPaperInfoCheck(exportModel, response);
+            return new ResponseEntity<>(HttpStatus.OK);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new StatusException("Q-050200", e.getMessage());
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "导出整个考试下所有 课程的试卷、答案、试卷结构", notes = "导出整个考试下所有 课程的试卷、答案、试卷结构")
+    @GetMapping(value = "/exportBatchExamPaperInfo/{orgId}/{exportWay}/{examId}/{exportContentList}/{loginName}")
+    public void exportBatchExamPaperInfo(HttpServletResponse response,
+                                         @PathVariable String exportWay,
+                                         @PathVariable String orgId,
+                                         @PathVariable String examId,
+                                         @PathVariable String exportContentList,
+                                         @PathVariable String loginName) {
+        ExportPaperInfoModel exportModel = new ExportPaperInfoModel();
+        exportModel.setExportWay(ExportWay.strToEnum(exportWay));
+        exportModel.setExamId(examId);
+        String[] exportContentArray = exportContentList.split(",");
+        List<String> list = new ArrayList<>();
+        for (int i = 0; i < exportContentArray.length; i++) {
+            list.add(exportContentArray[i]);
+        }
+        exportModel.setExportContentList(list);
+        try {
+            extractConfigFileService.exportExamPaperInfo(exportModel, response, loginName, orgId, null);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "根据试卷id抽取考试试卷", notes = "根据试卷id抽取考试试卷")
+    @GetMapping(value = "/extract/paper/{paperId}")
+    public ResponseEntity<Object> extractPaper(@PathVariable String paperId) {
+        try {
+            Map<String, Object> returnMap = extractConfigService.extractPaper(paperId);
+            if (returnMap.get("errorMsg") == null) {
+                PaperDto paperDto = (PaperDto) returnMap.get("paperDto");
+                return new ResponseEntity<>(paperDto, HttpStatus.OK);
+            } else {
+                return new ResponseEntity<>(returnMap.get("errorMsg") + "", HttpStatus.OK);
+            }
+        } catch (Exception e) {
+            logger.error("抽卷失败", e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "测试获取试卷Dto", notes = "测试获取试卷Dto")
+    @GetMapping(value = "/extract/getPaperDto/{paperId}")
+    public ResponseEntity<Object> getPaperDto(@PathVariable String paperId) {
+        try {
+            PaperDto paperDto = extractConfigService.getPaperDtoByPaperNew(paperId);
+            return new ResponseEntity<>(paperDto, HttpStatus.OK);
+        } catch (Exception e) {
+            logger.error("获取试卷Dto失败", e);
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "通过试卷id得到答案", notes = "通过试卷id得到答案")
+    @GetMapping(value = "/extract/getAnswerHtml/{paperId}")
+    public ResponseEntity<Object> getAnswerHtml(@PathVariable String paperId) {
+        String answerHtml = extractConfigService.getAnswerHtml(paperId);
+        return new ResponseEntity<>(answerHtml, HttpStatus.OK);
+    }
+
+}	

+ 191 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/GenPaperController.java

@@ -0,0 +1,191 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+import cn.com.qmth.examcloud.core.questions.base.enums.RandomGenPaperPolicy;
+import cn.com.qmth.examcloud.core.questions.service.PaperService;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.GenPaperDto;
+import cn.com.qmth.examcloud.core.questions.service.impl.GenPaperService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by weiwenhai on 16/12/28.
+ * update by wwh on 2018/5/21
+ *
+ * @describle 重写简易组卷逻辑
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class GenPaperController extends ControllerSupport {
+
+    @Autowired
+    GenPaperService genPaperService;
+
+    @Autowired
+    PaperService paperService;
+
+    @ApiOperation(value = "简易随机组卷", notes = "简易随机组卷")
+    @PostMapping("/genPaper/simple")
+    public ResponseEntity<Object> genPaperSimple(@RequestBody GenPaperDto genPaperDto) {
+        User user = getAccessUser();
+        genPaperDto.setOrgId(user.getRootOrgId().toString());
+        genPaperDto.setCreator(user.getDisplayName());
+        Map<String, Object> paperMap = new HashMap<>();
+        String paperName = genPaperDto.getPaperName();
+        try {
+            //判断试卷名称是否一样
+            boolean result = false;
+            if (genPaperDto.getGenNumber() > 1) {
+                for (int i = 1; i <= genPaperDto.getGenNumber(); i++) {
+                    genPaperDto.setPaperName(paperName + "_" + i);
+                    result = paperService.checkPaperName(genPaperDto.getPaperName(), PaperType.GENERATE, user.getRootOrgId() + "");
+                    if (!result) {
+                        paperMap.put("msg", "考试试卷:" + genPaperDto.getPaperName() + "已经存在");
+                        return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+                    }
+                }
+            }
+            result = paperService.checkPaperName(genPaperDto.getPaperName(), PaperType.GENERATE, user.getRootOrgId() + "");
+            if (!result) {
+                paperMap.put("msg", "考试试卷:" + genPaperDto.getPaperName() + "已经存在");
+                return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+            }
+            //如果是组一套试卷
+            if (genPaperDto.getGenNumber() == 1) {
+                //判断抽题类型  按数量  按分数
+                if (genPaperDto.getSimpleGenPaperPolicy() == RandomGenPaperPolicy.BY_QUESTIONNUM) {
+                    //按题目数量组卷
+                    paperMap = genPaperService.genPaperByQuestionNum(genPaperDto, user);
+                } else {
+                    // 按分数 组卷
+                    paperMap = genPaperService.genPaperByScore(genPaperDto, user);
+                }
+            } else {
+                //如果是组多套试卷
+                for (int i = 1; i <= genPaperDto.getGenNumber(); i++) {
+                    genPaperDto.setPaperName(paperName + "_" + i);
+                    //判断抽题类型  按数量  按分数
+                    if (genPaperDto.getSimpleGenPaperPolicy() == RandomGenPaperPolicy.BY_QUESTIONNUM) {
+                        paperMap.putAll(genPaperService.genPaperByQuestionNum(genPaperDto, user));
+                    } else {
+                        paperMap.putAll(genPaperService.genPaperByScore(genPaperDto, user));
+                    }
+                }
+            }
+            if (!paperMap.get("msg").equals("success")) {
+                return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+            }
+        } catch (Exception e) {
+            return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+        return new ResponseEntity<>(paperMap, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "精细组卷", notes = "精细组卷")
+    @PostMapping("/genPaper/normal")
+    public ResponseEntity<Object> genPaperNormal(@RequestBody GenPaperDto genPaperDto) {
+        User user = getAccessUser();
+        genPaperDto.setOrgId(user.getRootOrgId().toString());
+        genPaperDto.setCreator(user.getDisplayName());
+        Map<String, Object> paperMap = new HashMap<>();
+        String paperName = genPaperDto.getPaperName();
+        //判断试卷名称是否一样
+        try {
+            //判断试卷名称是否一样
+            boolean result = false;
+            if (genPaperDto.getGenNumber() > 1) {
+                for (int i = 1; i <= genPaperDto.getGenNumber(); i++) {
+                    genPaperDto.setPaperName(paperName + "_" + i);
+                    result = paperService.checkPaperName(genPaperDto.getPaperName(), PaperType.GENERATE, user.getRootOrgId() + "");
+                    if (!result) {
+                        paperMap.put("msg", "考试试卷:" + genPaperDto.getPaperName() + "已经存在");
+                        return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+                    }
+                }
+            }
+            result = paperService.checkPaperName(genPaperDto.getPaperName(), PaperType.GENERATE, user.getRootOrgId() + "");
+            if (!result) {
+                paperMap.put("msg", "考试试卷:" + genPaperDto.getPaperName() + "已经存在");
+                return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+            }
+        } catch (Exception e) {
+            return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+        if (genPaperDto.getGenNumber() > 1) {
+            for (int i = 1; i <= genPaperDto.getGenNumber(); i++) {
+                genPaperDto.setPaperName(paperName + "_" + i);
+                paperMap.putAll(genPaperService.genPaper(genPaperDto));
+                if (!paperMap.get("msg").equals("success")) {
+                    break;
+                }
+            }
+        } else if (genPaperDto.getGenNumber() == 1) {
+            paperMap = genPaperService.genPaper(genPaperDto);
+        }
+        if (paperMap.get("msg").equals("success")) {
+            return new ResponseEntity<>(paperMap, HttpStatus.OK);
+        } else {
+            return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+    @ApiOperation(value = "蓝图组卷", notes = "蓝图组卷")
+    @PostMapping("/genPaper/blue")
+    public ResponseEntity<Object> genPaperBlue(@RequestBody GenPaperDto genPaperDto) {
+        User user = getAccessUser();
+        genPaperDto.setOrgId(user.getRootOrgId().toString());
+        genPaperDto.setCreator(user.getDisplayName());
+        Map<String, Object> paperMap = new HashMap<>();
+        String paperName = genPaperDto.getPaperName();
+        //判断试卷名称是否一样
+        try {
+            //判断试卷名称是否一样
+            boolean result = false;
+            if (genPaperDto.getGenNumber() > 1) {
+                for (int i = 1; i <= genPaperDto.getGenNumber(); i++) {
+                    genPaperDto.setPaperName(paperName + "_" + i);
+                    result = paperService.checkPaperName(genPaperDto.getPaperName(), PaperType.GENERATE, user.getRootOrgId() + "");
+                    if (!result) {
+                        paperMap.put("msg", "考试试卷:" + genPaperDto.getPaperName() + "已经存在");
+                        return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+                    }
+                }
+            }
+            result = paperService.checkPaperName(genPaperDto.getPaperName(), PaperType.GENERATE, user.getRootOrgId() + "");
+            if (!result) {
+                paperMap.put("msg", "考试试卷:" + genPaperDto.getPaperName() + "已经存在");
+                return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+            }
+        } catch (Exception e) {
+            return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+        if (genPaperDto.getGenNumber() > 1) {
+            for (int i = 1; i <= genPaperDto.getGenNumber(); i++) {
+                genPaperDto.setPaperName(paperName + "_" + i);
+                paperMap.putAll(genPaperService.genBluePaper(genPaperDto));
+                if (!paperMap.get("msg").equals("success")) {
+                    break;
+                }
+            }
+        } else if (genPaperDto.getGenNumber() == 1) {
+            paperMap = genPaperService.genBluePaper(genPaperDto);
+        }
+        if (paperMap.get("msg").equals("success")) {
+            return new ResponseEntity<>(paperMap, HttpStatus.OK);
+        } else {
+            return new ResponseEntity<>(paperMap, HttpStatus.INTERNAL_SERVER_ERROR);
+        }
+    }
+
+}

+ 139 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ImportPaperController.java

@@ -0,0 +1,139 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.validation.constraints.NotNull;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.commons.CommonsMultipartFile;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.exception.PaperException;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+import cn.com.qmth.examcloud.core.questions.service.ClonePaperService;
+import cn.com.qmth.examcloud.core.questions.service.ImportDdCollegePaperService;
+import cn.com.qmth.examcloud.core.questions.service.ImportPaperService;
+import cn.com.qmth.examcloud.core.questions.service.temp.CqdxService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * @author weiwenhai
+ * @date 2017年4月14日 下午6:05:37
+ * @company QMTH
+ * @description 导入试卷控制器
+ * @code 090
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class ImportPaperController extends ControllerSupport {
+	protected static final Logger LOGGER = LoggerFactory.getLogger(ImportPaperController.class);
+	@Autowired
+	private ClonePaperService clonePaperService;
+	@Autowired
+	private ImportPaperService importPaperService;
+	@Autowired
+	private ImportDdCollegePaperService importDdCollegePaperService;
+	@Autowired
+	private CqdxService cqdxService;
+
+	/**
+	 * 导入试卷
+	 *
+	 * @param file
+	 * @return
+	 */
+	@ApiOperation(value = "导入试卷", notes = "导入试卷")
+	@PostMapping(value = "/importPaper")
+	public ResponseEntity<Object> importPaper(@ModelAttribute Paper paper,
+			@RequestParam("file") CommonsMultipartFile file) {
+		User user = getAccessUser();
+		LOGGER.info("导入开始");
+		File tempFile = null;
+		try {
+			tempFile = importPaperService.getUploadFile(file);
+			Paper newPaper = importPaperService.importPaper(paper, user, tempFile);
+			return new ResponseEntity<>(newPaper, HttpStatus.OK);
+		} catch (Exception e) {
+			LOGGER.error("导入异常:" + e.getMessage());
+			throw new StatusException("Q-090072", e.getMessage());
+		} finally {
+			LOGGER.info("导入结束");
+		}
+	}
+
+	/**
+	 * 新增试卷
+	 *
+	 * @param courseNo
+	 * @param paperName
+	 * @return
+	 */
+	@ApiOperation(value = "保存导入类型空白试卷", notes = "保存导入类型空白试卷")
+	@PostMapping(value = "/importPaper/saveBlankPaper/{courseNo}/{paperName}")
+	public ResponseEntity<Object> saveBlankPaper(@PathVariable String courseNo, @PathVariable String paperName) {
+		User user = getAccessUser();
+		Map<String, Object> returnMap = importPaperService.saveBlankPaper(courseNo, paperName, user);
+		if (returnMap.get("msg").equals("success")) {
+			return new ResponseEntity<>(returnMap, HttpStatus.OK);
+		} else {
+			return new ResponseEntity<>(returnMap, HttpStatus.INTERNAL_SERVER_ERROR);
+		}
+	}
+
+	/**
+	 * 克隆试卷
+	 *
+	 * @return
+	 */
+	@ApiOperation(value = "克隆试卷", notes = "克隆试卷")
+	@PostMapping(value = "/clonePaper/{paperId}/{paperName}/{courseNo}")
+	public ResponseEntity<Object> clonePaper(@PathVariable String paperId, @PathVariable String paperName,
+			@PathVariable String courseNo) {
+		User user = getAccessUser();
+		Map<String, Object> map = new HashMap<>();
+		try {
+			clonePaperService.clonePaper(paperId, paperName, courseNo, user);
+			map.put("msg", "success");
+			return new ResponseEntity<>(map, HttpStatus.OK);
+		} catch (PaperException e) {
+			map.put("msg", e.getMessage());
+			return new ResponseEntity<>(map, HttpStatus.INTERNAL_SERVER_ERROR);
+		}
+	}
+
+	@ApiOperation(value = "导入地大试卷", notes = "导入地大试卷")
+	@PostMapping(value = "/importDdCollegePaper")
+	public ResponseEntity<Object> importDdCollegePaper(
+			@RequestPart @NotNull(message = "上传文件不能为空!") MultipartFile dataFile, @RequestParam Long rootOrgId) {
+		User user = getAccessUser();
+		importDdCollegePaperService.importDdCollegePaper(dataFile, user, rootOrgId);
+		return new ResponseEntity<>(HttpStatus.OK);
+	}
+
+	@ApiOperation(value = "导入重庆试卷", notes = "导入重庆试卷")
+	@PostMapping(value = "/importCqCollegePaper")
+	public ResponseEntity<Object> importCqCollegePaper(
+			@RequestPart @NotNull(message = "上传文件不能为空!") MultipartFile dataFile, @RequestParam Long rootOrgId,
+			@RequestParam String paperNameSuffix, @RequestParam String impType) {
+		User user = getAccessUser();
+		cqdxService.bulidPaper(dataFile, user, rootOrgId, paperNameSuffix,impType);
+		return new ResponseEntity<>(HttpStatus.OK);
+	}
+
+}

+ 148 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/IndexController.java

@@ -0,0 +1,148 @@
+/*
+ * *************************************************
+ * Copyright (c) 2019 QMTH. All Rights Reserved.
+ * Created by Deason on 2019-03-07 13:53:38.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.core.questions.base.enums.ExamFileType;
+import cn.com.qmth.examcloud.core.questions.base.enums.ExportWay;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
+import cn.com.qmth.examcloud.core.questions.service.ExportPaperService;
+import cn.com.qmth.examcloud.core.questions.service.ExtractConfigFileService;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.ExportPaperInfoModel;
+import cn.com.qmth.examcloud.core.questions.service.temp.CqdxService;
+import cn.com.qmth.examcloud.core.questions.service.temp.DdExcelService;
+import cn.com.qmth.examcloud.web.support.Naked;
+import cn.com.qmth.examcloud.web.support.SpringContextHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.ArrayList;
+import java.util.List;
+
+@Controller
+@RequestMapping("${api_cqb}/")
+public class IndexController {
+    private static final Logger log = LoggerFactory.getLogger(IndexController.class);
+
+    @Naked
+    @ResponseBody
+    @RequestMapping(value = "/paper/init", method = RequestMethod.GET)
+    public String init(HttpServletRequest request, HttpServletResponse response) throws Exception {
+        /* 重庆大学xml转paper对象入库 */
+        CqdxService cqdxService = SpringContextHolder.getBean(CqdxService.class);
+        //cqdxService.bulidPaper();
+
+        DdExcelService ddExcelService = SpringContextHolder.getBean(DdExcelService.class);
+        //ddExcelService.exportPaperStructExcel("1407");
+        //ddExcelService.exporExcel("0");
+
+        //exportExtractExcel(response);
+        //exportQuestionExcel();
+        //exportQuestionStatistic(response);
+
+        return "ok";
+    }
+
+    private void exportExtractExcel(HttpServletResponse response) {
+        //用来导出调卷规则中,生成的主观题和客观题的Excel(导出调卷规则)
+        ExtractConfigFileService service = SpringContextHolder.getBean(ExtractConfigFileService.class);
+        ExportPaperInfoModel model = new ExportPaperInfoModel();
+        model.setExamId("606");//for export online
+        model.setExportWay(ExportWay.BATCH);
+        List<String> list = new ArrayList<>();
+        list.add(ExamFileType.PAPER_STRUCTURE_OBJECTIVE.name());
+        model.setExportContentList(list);
+        try {
+            service.exportExamPaperInfo(model, response, "feng", null, null);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    private void exportQuestionExcel() {
+        //用来导出给定课程关联中的主观题信息的Excel(导出课程下的主观题试题分布)
+        DdExcelService ddExcelService = SpringContextHolder.getBean(DdExcelService.class);
+        List<String> codes = new ArrayList<>();
+//        codes.add("D02020001");
+//        codes.add("D07020005");
+//        codes.add("D07020022");
+//        codes.add("D08020006");
+//        codes.add("D08020008");
+//        codes.add("D08020009");
+//        codes.add("D08020013");
+
+        codes.add("D17020004");
+        codes.add("D17020003");
+        codes.add("F06020036");
+        codes.add("D16020003");
+        codes.add("F46020001");
+        codes.add("F32020009");
+        codes.add("D17020007");
+        codes.add("D15020003");
+        codes.add("D15020002");
+        codes.add("D12020003");
+        codes.add("D11020015");
+        codes.add("D08020016");
+        codes.add("D06020024");
+        codes.add("D06020009");
+        codes.add("D05020025");
+        codes.add("D05020020");
+        codes.add("D05020001");
+
+        try {
+            ddExcelService.exportSubQues("1627", 481L, codes);
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    private void exportQuestionStatistic(HttpServletResponse response) throws Exception {
+        ExportPaperService exportPaperService = SpringContextHolder.getBean(ExportPaperService.class);
+
+        List<Course> courses = new ArrayList<>();
+        courses.add(new Course("课程0627", "course_0627"));
+        courses.add(new Course("课程0628", "course_0628"));
+
+//        courses.add(new Course("仓储与配送管理", "183007"));
+//        courses.add(new Course("操作系统原理", "021008"));
+//        courses.add(new Course("弹性理论", "051020"));
+//        courses.add(new Course("电子商务信息安全", "193004"));
+//        courses.add(new Course("供应链管理", "183010"));
+//        courses.add(new Course("国际经济法学", "071014"));
+//        courses.add(new Course("行政事业管理", "212003"));
+//        courses.add(new Course("汇编语言", "021009"));
+//        courses.add(new Course("基于INTERNET的管理信息系统", "163001"));
+//        courses.add(new Course("计算机软件技术基础(1)", "041020"));
+//        courses.add(new Course("建设工程计量", "282002"));
+//        courses.add(new Course("软件工程", "021014"));
+//        courses.add(new Course("商品学概论", "183002"));
+//        courses.add(new Course("信息经济与信息管理", "011009"));
+//        courses.add(new Course("中国法制史学", "071008"));
+//        courses.add(new Course("财务会计", "061009"));
+//        courses.add(new Course("成本会计", "061007"));
+//        courses.add(new Course("大学语文", "001030"));
+//        courses.add(new Course("法理学", "071002"));
+//        courses.add(new Course("房地产开发与经营", "092005"));
+//        courses.add(new Course("房屋建筑学(工业厂房)", "051018"));
+//        courses.add(new Course("房屋建筑学(民用建筑)", "051017"));
+//        courses.add(new Course("分析化学", "532004"));
+//        courses.add(new Course("工程地质学", "051003"));
+//        courses.add(new Course("工程概预算", "051015"));
+//        courses.add(new Course("工程力学", "423002"));
+//        courses.add(new Course("工程造价案例分析", "282003"));
+
+        //exportPaperService.downQuestionDistribute(courses.get(0).getCode(), response);
+        exportPaperService.downQuestionDistributes(courses);
+    }
+
+}

+ 34 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/InitQuesHashController.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.core.questions.service.initQuesHash.InitQuesHashService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author weiwenhai
+ * @date 2018.7.17
+ * @company qmth
+ * @describle 初始化所有题目题干的Hash
+ */
+
+@RestController
+@RequestMapping("${api_cqb}/")
+public class InitQuesHashController extends ControllerSupport {
+
+    @Autowired
+    private InitQuesHashService initQuesHashService;
+
+    @ApiOperation(value = "根据机构初始化题干hash", notes = "根据机构初始化题干hash")
+    @GetMapping(value = "/initQuesHash/{orgId}")
+    public ResponseEntity<Object> initQuesHash(@PathVariable String orgId) {
+        initQuesHashService.initHash(orgId);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+}

+ 727 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperController.java

@@ -0,0 +1,727 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.BeanCopierUtil;
+import cn.com.qmth.examcloud.core.questions.base.SpringContextUtils;
+import cn.com.qmth.examcloud.core.questions.base.StringSimilarityUtils;
+import cn.com.qmth.examcloud.core.questions.base.enums.ExportTemplateType;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.ExportServiceManageRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExportServiceManage;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperSearchInfo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.service.PaperService;
+import cn.com.qmth.examcloud.core.questions.service.QuesService;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperDetailExp;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperDetailUnitExp;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperExp;
+import cn.com.qmth.examcloud.core.questions.service.bean.paper.PreviewPaperHandler;
+import cn.com.qmth.examcloud.core.questions.service.config.SysProperty;
+import cn.com.qmth.examcloud.core.questions.service.export.ExportPaperAbstractService;
+import cn.com.qmth.examcloud.core.questions.service.util.ExportTemplateUtil;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import cn.com.qmth.examcloud.web.support.Naked;
+import freemarker.template.TemplateException;
+
+import com.google.gson.Gson;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Example;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Created by songyue on 16/12/28.
+ * updated by weiwenhai on 2018.9.28
+ *
+ * @code 160
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class PaperController extends ControllerSupport {
+    private final static String BASE_PAGE="base_page";
+    @Autowired
+    PaperService paperService;
+
+    @Autowired
+    PaperRepo paperRepo;
+
+    @Autowired
+    Gson gson;
+
+    @Autowired
+    SysProperty sysProperty;
+
+    @Autowired
+    QuesService quesService;
+
+    @Autowired
+    ExportServiceManageRepo exportServiceManageRepo;
+
+    /**
+     * 根据Id获取试卷
+     *
+     * @param paperId
+     * @return
+     */
+    @Naked
+    @ResponseBody
+    @ApiOperation(value = "根据Id获取试卷", notes = "根据Id获取试卷")
+    @GetMapping(value = "/paper/{paperId}")
+    public ResponseEntity<Object> getPaperById(@PathVariable String paperId) {
+        return new ResponseEntity<>(paperService.getPaperDto(paperId), HttpStatus.OK);
+    }
+
+    /**
+     * 保存试卷
+     *
+     * @param paper
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "保存试卷", notes = "保存试卷")
+    @PutMapping(value = "/paper")
+    public ResponseEntity<Object> savePaperById(@RequestBody PaperExp paper) {
+        User user = getAccessUser();
+        Map<String, Object> msgMap = paperService.savePaper(paper, user);
+        if ("success".equals(msgMap.get("msg"))) {
+            return new ResponseEntity<>(msgMap, HttpStatus.OK);
+        } else {
+            throw new StatusException("1000",msgMap.get("msg").toString());
+        }
+    }
+
+    /**
+     * 查询所有导入试卷
+     *
+     * @param paperSearchInfo
+     * @param curPage
+     * @param pageSize
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "查询所有导入试卷", notes = "查询所有导入试卷")
+    @GetMapping(value = "/importPaper/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getImportPapers(@ModelAttribute PaperSearchInfo paperSearchInfo,
+                                                  @PathVariable int curPage,
+                                                  @PathVariable int pageSize) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        return new ResponseEntity<>(paperService.getImportPapers(paperSearchInfo, curPage, pageSize), HttpStatus.OK);
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "查询所有待审核和审核不通过的导入试卷", notes = "查询所有待审核和审核不通过的导入试卷")
+    @GetMapping(value = "/importPaperNotSuccess/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getImportPapersNotSuccess(@ModelAttribute PaperSearchInfo paperSearchInfo, @PathVariable int curPage, @PathVariable int pageSize) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        return new ResponseEntity<>(paperService.getImportPapersNotSuccess(paperSearchInfo, curPage, pageSize),
+                HttpStatus.OK);
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "根据条件查询导入试卷", notes = "根据条件查询导入试卷")
+    @GetMapping(value = "/getImportPaper")
+    public ResponseEntity<List<Paper>> getImportPapersBySearch(@ModelAttribute PaperSearchInfo paperSearchInfo) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        return new ResponseEntity<List<Paper>>(paperService.getImportPapersBySearch(paperSearchInfo), HttpStatus.OK);
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "根据条件查询已组试卷", notes = "根据条件查询已组试卷")
+    @GetMapping(value = "/getGenPaper")
+    public ResponseEntity<List<Paper>> getGenPapersBySearch(@ModelAttribute PaperSearchInfo paperSearchInfo) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        return new ResponseEntity<>(paperService.getGenPapersBySearch(paperSearchInfo), HttpStatus.OK);
+    }
+
+    /**
+     * 查询所有已组试卷
+     *
+     * @param paperSearchInfo
+     * @param curPage
+     * @param pageSize
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "查询所有已组试卷", notes = "查询所有已组试卷")
+    @GetMapping(value = "/genPaper/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getGenPapers(@ModelAttribute PaperSearchInfo paperSearchInfo,
+                                               @PathVariable int curPage, @PathVariable int pageSize) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        return new ResponseEntity<>(paperService.getGenPapers(paperSearchInfo, curPage, pageSize), HttpStatus.OK);
+    }
+
+    /**
+     * @param paperSearchInfo
+     * @param ids
+     * @param curPage
+     * @param pageSize
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "查询所有导入试卷(not in paperIds)", notes = "查询所有导入试卷(not in paperIds)")
+    @GetMapping(value = "/genPaper/{ids}/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getGenPapersNotInIds(@ModelAttribute PaperSearchInfo paperSearchInfo,
+                                                       @PathVariable String[] ids, @PathVariable int curPage, @PathVariable int pageSize) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        if (ids != null && ids.length > 0) {
+            return new ResponseEntity<>(paperService.getPapersNotInIds(paperSearchInfo, ids, curPage, pageSize, PaperType.GENERATE),
+                    HttpStatus.OK);
+        } else {
+            return new ResponseEntity<>(paperService.getGenPapers(paperSearchInfo, curPage, pageSize), HttpStatus.OK);
+        }
+    }
+
+    /**
+     * 删除试卷
+     *
+     * @param paperIds
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "删除试卷", notes = "删除试卷")
+    @DeleteMapping(value = "/paper/{paperIds}")
+    public ResponseEntity<Object> delPaper(@PathVariable String paperIds) {
+        List<String> paperList = Stream.of(paperIds.split(",")).collect(Collectors.toList());
+        Map<String, Object> msgMap = paperService.deletePapers(paperList);
+        if (msgMap.get("msg").equals("success")) {
+            return new ResponseEntity<>(msgMap, HttpStatus.OK);
+        } else {
+            throw new StatusException("1001","试卷["+msgMap.get("paperName")+"]中有试题被组卷使用,不能删除!");
+        }
+
+    }
+
+    /**
+     * 批量通过试卷
+     *
+     * @param paperSearchInfo
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "批量通过试卷", notes = "批量通过试卷")
+    @PutMapping(value = "/paper/pass")
+    public ResponseEntity<Object> passPapers(@RequestBody PaperSearchInfo paperSearchInfo) {
+        paperService.passPapers(Arrays.asList(paperSearchInfo.getPaperIds()));
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 批量不通过试卷
+     *
+     * @param paperSearchInfo
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "批量不通过试卷", notes = "批量不通过试卷")
+    @PutMapping(value = "/paper/noPass")
+    public ResponseEntity<Object> noPassPapers(@RequestBody PaperSearchInfo paperSearchInfo) {
+        paperService.noPassPapers(Arrays.asList(paperSearchInfo.getPaperIds()));
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 批量待审核试卷
+     *
+     * @param paperSearchInfo
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "批量待审核试卷", notes = "批量待审核试卷")
+    @PutMapping(value = "/paper/draft")
+    public ResponseEntity<Object> initPapers(@RequestBody PaperSearchInfo paperSearchInfo) {
+        paperService.backPapers(Arrays.asList(paperSearchInfo.getPaperIds()));
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 获取卷库考试试卷
+     *
+     * @param examId
+     * @param courseCode
+     * @param groupCode
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "获取卷库考试试卷", notes = "获取卷库考试试卷")
+    @GetMapping(value = "/paper/list/{examId}/{courseCode}/{groupCode}")
+    public List<Paper> listPaperById(@PathVariable String examId, @PathVariable String courseCode,
+                                     @PathVariable String groupCode) {
+        return paperService.listExamPapers(Long.parseLong(examId), courseCode, groupCode);
+    }
+
+    /**
+     * 新增考试试卷
+     *
+     * @param examId
+     * @param courseCode
+     * @param groupCode
+     * @param paperId
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "新增考试试卷", notes = "新增考试试卷")
+    @PostMapping(value = "/paper/join/{examId}/{courseCode}/{groupCode}/{paperId}")
+    public ResponseEntity<Object> joinExamPaper(@PathVariable String examId, @PathVariable String courseCode,
+                                                @PathVariable String groupCode, @PathVariable String paperId) {
+        paperService.joinToExamPaper(Long.parseLong(examId), courseCode, groupCode, paperId);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 删除考试试卷
+     *
+     * @param examId
+     * @param courseCode
+     * @param groupCode
+     * @param paperId
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "删除考试试卷", notes = "删除考试试卷")
+    @DeleteMapping(value = "/paper/release/{examId}/{courseCode}/{groupCode}/{paperId}")
+    public ResponseEntity<Object> releaseExamPaper(@PathVariable String examId, @PathVariable String courseCode,
+                                                   @PathVariable String groupCode, @PathVariable String paperId) {
+
+        paperService.releaseExamPaper(Long.parseLong(examId), courseCode, groupCode, paperId);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 获取卷库考试试卷类型集合
+     *
+     * @param examId
+     * @param courseCode
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "获取考试试卷类型集合", notes = "获取考试试卷类型集合")
+    @GetMapping(value = "/paper/groupCode/{examId}/{courseCode}")
+    public Set<String> listGroup(@PathVariable String examId, @PathVariable String courseCode) {
+        return paperService.listGroupCodes(Long.parseLong(examId), courseCode);
+    }
+
+    /**
+     * 删除考试试卷类型
+     *
+     * @param examId
+     * @param courseCode
+     * @param groupCode
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "删除考试试卷类型", notes = "删除考试试卷类型")
+    @DeleteMapping(value = "/paper/groupCode/{examId}/{courseCode}/{groupCode}")
+    public ResponseEntity<Object> deleteGroup(@PathVariable String examId, @PathVariable String courseCode,
+                                              @PathVariable String groupCode) {
+        paperService.deletGroupCode(Long.parseLong(examId), courseCode, groupCode);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 删除导入试卷中的试题
+     *
+     * @param questionId
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "删除导入试卷中的试题", notes = "删除导入试卷中的试题")
+    @DeleteMapping(value = "/paper/deleteQuestion/{questionId}")
+    public ResponseEntity<Object> deleteImportPaperQuestion(@PathVariable String questionId) {
+        User user = getAccessUser();
+        List<String> paperNames = paperService.deleteImportQuestionById(questionId, user);
+        return new ResponseEntity<>(paperNames, HttpStatus.OK);
+    }
+
+    /**
+     * 向导入试卷中的新增试题
+     *
+     * @param paperId
+     * @param paperDetailId
+     * @param question
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "向导入试卷中的新增试题", notes = "向导入试卷中的新增试题")
+    @PostMapping(value = "/paper/addQuestion/{paperId}/{paperDetailId}")
+    public ResponseEntity<Object> insertQuestionToPaper(@PathVariable String paperId,
+                                                        @PathVariable String paperDetailId, @RequestBody Question question) {
+        User user = getAccessUser();
+        return new ResponseEntity<>(paperService.insertQuestionToPaper(paperId, paperDetailId, question, user),
+                HttpStatus.OK);
+
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "获取试卷的重复试题", notes = "获取试卷的重复试题")
+    @GetMapping(value = "/paper/{paperId}/reduplicate-questions")
+    public ResponseEntity<Object> reduplicateQuestions(@PathVariable String paperId) {
+        List<PaperDetailUnitExp> allPaperDetailUnitList = new ArrayList<>();
+        List<Set<String>> reduplicateId = new ArrayList<>();
+        PaperExp paperExp = paperService.getPaperDto(paperId);
+        List<PaperDetailExp> paperDetailExpList = paperExp.getPaperDetails();
+        if (paperDetailExpList != null && paperDetailExpList.size() > 0) {
+            for (PaperDetailExp paperDetailExp : paperDetailExpList) {
+                if (paperDetailExp.getPaperDetailUnits() != null) {
+                    allPaperDetailUnitList.addAll(paperDetailExp.getPaperDetailUnits());
+                }
+            }
+        }
+        int length = allPaperDetailUnitList.size();
+        for (int i = 0; i < length - 1; i++) {
+            PaperDetailUnitExp paperDetailUnitExp1 = allPaperDetailUnitList.get(i);
+            String quesText1 = quesService.getExtractText(paperDetailUnitExp1.getQuestion());
+            if (StringUtils.isEmpty(quesText1)) {
+                continue;
+            }
+            for (int j = i + 1; j < length; j++) {
+                PaperDetailUnitExp paperDetailUnitExp2 = allPaperDetailUnitList.get(j);
+                String quesText2 = quesService.getExtractText(paperDetailUnitExp2.getQuestion());
+                if (StringUtils.isEmpty(quesText2)) {
+                    continue;
+                }
+                double similarity = StringSimilarityUtils.getSimilarityWithCosinesBySeg(quesText1, quesText2);
+                if (similarity > sysProperty.getReduplicateSimilarity()) {
+                    boolean found = false;
+                    for (int k = 0; k < reduplicateId.size(); k++) {
+                        if (reduplicateId.get(k).contains(paperDetailUnitExp1.getId())
+                                && !reduplicateId.get(k).contains(paperDetailUnitExp2.getId())) {
+                            found = true;
+                            reduplicateId.get(k).add(paperDetailUnitExp2.getId());
+                            break;
+                        } else if (!reduplicateId.get(k).contains(paperDetailUnitExp1.getId())
+                                && reduplicateId.get(k).contains(paperDetailUnitExp2.getId())) {
+                            found = true;
+                            reduplicateId.get(k).add(paperDetailUnitExp1.getId());
+                            break;
+                        } else if (reduplicateId.get(k).contains(paperDetailUnitExp1.getId())
+                                && reduplicateId.get(k).contains(paperDetailUnitExp2.getId())) {
+                            found = true;
+                            // 两个题都在分组里,就不加了
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        Set<String> redupSet = new HashSet<>();
+
+                        redupSet.add(paperDetailUnitExp1.getId());
+                        redupSet.add(paperDetailUnitExp2.getId());
+                        reduplicateId.add(redupSet);
+                    }
+                    // reduplicateId.add(paperDetailUnitExp1.getId());
+                    // reduplicateId.add(paperDetailUnitExp2.getId());
+                }
+            }
+        }
+        return new ResponseEntity<>(reduplicateId, HttpStatus.OK);
+    }
+
+    /**
+     * 根据试题获取试卷名称
+     *
+     * @param
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "获取试题所在的试卷名称", notes = "获取试题所在的试卷名称")
+    @GetMapping(value = "/paper/listNames/{questionId}")
+    public ResponseEntity<Object> getPaperNamesByQuesId(@PathVariable String questionId) {
+        return new ResponseEntity<>(paperService.getPaperNamesByQuestionId(questionId), HttpStatus.OK);
+    }
+
+    /**
+     * 根据试题获取试卷名称
+     *
+     * @param
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "查询用于选题的试题列表", notes = "查询用于选题的试题列表")
+    @GetMapping(value = "/paper/listQuestion/{paperId}/{curPage}/{pageSize}")
+    public ResponseEntity<Object> listQuestionforSelect(@PathVariable String paperId,
+                                                        @PathVariable int curPage,
+                                                        @PathVariable int pageSize,
+                                                        @RequestParam(name = "quesType") String quesType,
+                                                        @RequestParam(name = "quesBody") String quesBody) {
+        User user = getAccessUser();
+        if (user == null) {
+            return new ResponseEntity<>(new PageImpl<Question>(new ArrayList<>()), HttpStatus.OK);
+        }
+        QuesStructType quesStructType = null;
+        if (StringUtils.isNotEmpty(quesType)) {
+            quesStructType = QuesStructType.valueOf(quesType);
+        }
+        Page<Question> questionPageList = paperService.listQuestionforSelect(paperId, curPage, pageSize, quesStructType, user, quesBody);
+        return new ResponseEntity<>(questionPageList, HttpStatus.OK);
+    }
+
+    /**
+     * 向试卷中某道大题插入多道试题(已存在的试题)
+     *
+     * @param paperId
+     * @param paperDetailId
+     * @param questions
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "向导入试卷中的新增试题", notes = "向导入试卷中的新增试题")
+    @PostMapping(value = "/paper/selectQuestions/{paperId}/{paperDetailId}")
+    public ResponseEntity<Object> selectQuestionsToPaper(@PathVariable String paperId,
+                                                         @PathVariable String paperDetailId, @RequestBody List<Question> questions) {
+        User user = getAccessUser();
+        return new ResponseEntity<>(paperService.selectQuestionsToPaper(paperId, paperDetailId, questions, user),
+                HttpStatus.OK);
+
+    }
+
+    /**
+     * @param paperSearchInfo
+     * @param ids
+     * @param curPage
+     * @param pageSize
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "查询所有导入试卷(not in paperIds)", notes = "查询所有导入试卷(not in paperIds)")
+    @GetMapping(value = "/importPaper/{ids}/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getImportPapersNotInIds(@ModelAttribute PaperSearchInfo paperSearchInfo, @PathVariable String[] ids, @PathVariable int curPage,
+                                                          @PathVariable int pageSize) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        if (ids != null && ids.length > 0) {
+            return new ResponseEntity<>(paperService.getPapersNotInIds(paperSearchInfo, ids, curPage, pageSize, PaperType.IMPORT),
+                    HttpStatus.OK);
+        } else {
+            return new ResponseEntity<>(paperService.getImportPapers(paperSearchInfo, curPage, pageSize), HttpStatus.OK);
+        }
+    }
+
+    /**
+     * @param paperSearchInfo
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "查询该课程的所有导入试卷", notes = "查询该课程的所有导入试卷")
+    @GetMapping(value = "/importPaper/course")
+    public ResponseEntity<Object> getImportPapersByCourseNo(@ModelAttribute PaperSearchInfo paperSearchInfo) {
+        User user = getAccessUser();
+        paperSearchInfo.setOrgId(user.getRootOrgId().toString());
+        paperService.formatPaperSearchInfo(paperSearchInfo);
+        Paper importPaper = BeanCopierUtil.copyProperties(paperSearchInfo, Paper.class);
+        importPaper.setPaperType(PaperType.IMPORT);
+        return new ResponseEntity<>(paperRepo.findAll(Example.of(importPaper)), HttpStatus.OK);
+
+    }
+
+    /**
+     * 使用原卷
+     *
+     * @param paperIds
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "复制导入试卷为组卷", notes = "复制导入试卷为组卷")
+    @PutMapping(value = "/useBasePaper/{paperIds}")
+    public ResponseEntity<Object> useBasePaper(@PathVariable String paperIds) {
+        User user = getAccessUser();
+        try {
+            String[] paperIdArray = paperIds.split(",");
+            for (int i = 0; i < paperIdArray.length; i++) {
+                Paper oldpaper = cn.com.qmth.examcloud.core.questions.base.Model.of(paperRepo.findById(paperIdArray[i]));
+                boolean result = paperService.checkPaperName(oldpaper.getName(), PaperType.GENERATE, user.getRootOrgId() + "");
+                if (!result) {
+                    throw new StatusException("160565", "考试试卷:" + oldpaper.getName() + "已经存在");
+                }
+            }
+            paperService.useBasePaper(paperIds, user.getDisplayName());
+            return new ResponseEntity<>(HttpStatus.OK);
+        } catch (Exception e) {
+            throw new StatusException("160537", e.getMessage());
+        }
+    }
+
+    /**
+     * 上传音频文件检查
+     *
+     * @param paperId
+     * @param filesName
+     * @return
+     */
+    @ResponseBody
+    @ApiOperation(value = "上传音频文件检查", notes = "上传音频文件检查")
+    @PostMapping(value = "/checkRadioFile/{paperId}")
+    public ResponseEntity<Object> checkRadioFile(@PathVariable String paperId, @RequestBody List<String> filesName) {
+        Map<String, String> errorMessage = paperService.checkRadioFile(paperId, filesName);
+        return new ResponseEntity<>(errorMessage, HttpStatus.OK);
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "上传音频文件", notes = "上传音频文件")
+    @PostMapping(value = "/uploadRadio/{paperId}")
+    public ResponseEntity<Object> uploadRadio(List<MultipartFile> files, @PathVariable String paperId) {
+        User user = getAccessUser();
+        paperService.uploadRadio(files, paperId, user);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @Naked
+    @ApiOperation(value = "预览试卷页面")
+    @GetMapping(value = "/paper/pdf/{paperId}")
+    public String viewPaper(Model model, @PathVariable String paperId, @RequestParam(required = false) String examName) {
+        Paper paper = cn.com.qmth.examcloud.core.questions.base.Model.of(paperRepo.findById(paperId));
+        if (paper == null) {
+            throw new StatusException("500", "试卷信息不存在!");
+        }
+
+        ExportServiceManage manager = exportServiceManageRepo.findByOrgId(paper.getOrgId());
+        if (manager == null) {
+            throw new StatusException("500", "当前学校尚未配置试卷模板!");
+        }
+
+        ExportPaperAbstractService exportPaperService = SpringContextUtils.getBean(ExportPaperAbstractService.class, manager.getExportServiceName());
+        PaperExp previewPaper = exportPaperService.previewPaperForPDF(paper);
+
+//        model.addAttribute("htmlList", PreviewPaperHandler.convertPaperHtml(previewPaper));
+//        model.addAttribute("orgName", manager.getOrgName());
+//        model.addAttribute("examName", examName);
+//        model.addAttribute("courseNo", previewPaper.getCourseNo());
+//        model.addAttribute("courseName", previewPaper.getCourseName());
+//        model.addAttribute("courseLevel", previewPaper.getCourseLevel());
+
+        //目前只“山东大学”和“陕师大”配了试卷模板
+//        if ("sddxExportPaperService".equals(manager.getExportServiceName())
+//                || "sxsfExportPaperService".equals(manager.getExportServiceName())) {
+//            return String.format("%s_paper", manager.getExportServiceName());
+//        } else {
+//            return "default_paper";
+//        }
+        StringWriter result = new StringWriter();
+        try {
+            Map<String, Object> map = new HashMap<String, Object>();
+            map.put("htmlList", PreviewPaperHandler.convertPaperHtml(previewPaper));
+            map.put("orgName", manager.getOrgName());
+            map.put("examName", examName);
+            map.put("courseNo", previewPaper.getCourseNo());
+            map.put("courseName", previewPaper.getCourseName());
+            map.put("courseLevel", previewPaper.getCourseLevel());
+            ExportTemplateUtil.getTemplate(Long.valueOf(paper.getOrgId()), ExportTemplateType.PAPER_VIEW).process(map, result);
+            String content=result.toString();
+            model.addAttribute("pageContent", content);
+            return BASE_PAGE;
+        } catch (NumberFormatException | TemplateException | IOException e) {
+            throw new StatusException("500", "解析模板出错!");
+        }finally {
+            try {
+                result.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    @Naked
+    @ApiOperation(value = "预览试卷答案页面")
+    @GetMapping(value = "/paper/answer/pdf/{paperId}")
+    public String viewPaperAnswer(Model model, @PathVariable String paperId, @RequestParam(required = false) String examName) {
+        Paper paper = cn.com.qmth.examcloud.core.questions.base.Model.of(paperRepo.findById(paperId));
+        if (paper == null) {
+            throw new StatusException("500", "试卷答案信息不存在!");
+        }
+
+        ExportServiceManage manager = exportServiceManageRepo.findByOrgId(paper.getOrgId());
+        if (manager == null) {
+            throw new StatusException("500", "当前学校尚未配置试卷模板!");
+        }
+
+        ExportPaperAbstractService exportPaperService = SpringContextUtils.getBean(ExportPaperAbstractService.class, manager.getExportServiceName());
+        PaperExp previewPaper = exportPaperService.previewPaperForPDF(paper);
+
+//        model.addAttribute("htmlList", PreviewPaperHandler.convertPaperAnswerHtml(previewPaper));
+//        model.addAttribute("orgName", manager.getOrgName());
+//        model.addAttribute("examName", examName);
+//        model.addAttribute("courseNo", previewPaper.getCourseNo());
+//        model.addAttribute("courseName", previewPaper.getCourseName());
+//        model.addAttribute("courseLevel", previewPaper.getCourseLevel());
+//
+//        //目前只“山东大学”和“陕师大”配了试卷模板
+//        if ("sddxExportPaperService".equals(manager.getExportServiceName())
+//                || "sxsfExportPaperService".equals(manager.getExportServiceName())) {
+//            return String.format("%s_answer", manager.getExportServiceName());
+//        } else {
+//            return "default_answer";
+//        }
+        
+        StringWriter result = new StringWriter();
+        try {
+            Map<String, Object> map = new HashMap<String, Object>();
+            map.put("htmlList", PreviewPaperHandler.convertPaperAnswerHtml(previewPaper));
+            map.put("orgName", manager.getOrgName());
+            map.put("examName", examName);
+            map.put("courseNo", previewPaper.getCourseNo());
+            map.put("courseName", previewPaper.getCourseName());
+            map.put("courseLevel", previewPaper.getCourseLevel());
+            ExportTemplateUtil.getTemplate(Long.valueOf(paper.getOrgId()), ExportTemplateType.ANWSER_VIEW).process(map, result);
+            String content=result.toString();
+            model.addAttribute("pageContent", content);
+            return BASE_PAGE;
+        } catch (NumberFormatException | TemplateException | IOException e) {
+            throw new StatusException("500", "解析模板出错!");
+        }finally {
+            try {
+                result.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    @ApiOperation(value = "传送到印刷平台", notes = "传送到印刷平台")
+    @GetMapping(value = "/sendPrint/{paperId}/{examId}/{orgId}")
+    public ResponseEntity<Object> sendPrint(@PathVariable String paperId,
+                                            @PathVariable String orgId,
+                                            @PathVariable String examId) {
+        paperService.sendPrint(paperId, orgId, examId);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "根绝试卷id查询不同类型,题的数量")
+    @GetMapping(value = "/paper/questionNumbers/{paperId}/{publicityType}/{difficultyType}")
+    public ResponseEntity<Object> questionNumbers(@PathVariable String paperId,
+                                                  @PathVariable Integer publicityType,
+                                                  @PathVariable Integer difficultyType) {
+        int total = paperService.getQuestionTypeNumbers(paperId, publicityType, difficultyType);
+        return new ResponseEntity<>(total, HttpStatus.OK);
+    }
+
+    @ResponseBody
+    @ApiOperation(value = "根绝试卷id查询不同类型,题的数量")
+    @GetMapping(value = "/paper/questionScores/{paperId}/{publicityType}/{difficultyType}")
+    public ResponseEntity<Object> questionScores(@PathVariable String paperId,
+                                                 @PathVariable Integer publicityType,
+                                                 @PathVariable Integer difficultyType) {
+        double total = paperService.getQuestionTypeScore(paperId, publicityType, difficultyType);
+        return new ResponseEntity<>(total, HttpStatus.OK);
+    }
+}

+ 115 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperDetailController.java

@@ -0,0 +1,115 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
+import cn.com.qmth.examcloud.core.questions.service.PaperDetailService;
+import cn.com.qmth.examcloud.core.questions.service.PaperService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+
+/**
+ * Created by songyue on 16/12/28.
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class PaperDetailController extends ControllerSupport {
+
+    @Autowired
+    PaperDetailService paperDetailService;
+
+    @Autowired
+    PaperService paperService;
+
+    @Autowired
+    PaperDetailRepo paperDetailRepo;
+
+    /**
+     * 获取大题对应的小题
+     *
+     * @param detail_id
+     * @return
+     */
+    @ApiOperation(value = "获取大题对应的小题", notes = "获取大题对应的小题")
+    @GetMapping(value = "/paperDetail/units/{detail_id}")
+    public List<PaperDetailUnit> getUnitsByPaperDetailId(String detail_id) {
+        return paperDetailService.getUnitsByPaperDetailId(detail_id);
+    }
+
+    /**
+     * 获取大题
+     *
+     * @param detail_id
+     * @return
+     */
+    @ApiOperation(value = "获取大题", notes = "获取大题")
+    @GetMapping(value = "/paperDetail/{detail_id}")
+    public PaperDetail getPaperDetailId(@PathVariable String detail_id) {
+        return paperDetailService.findById(detail_id);
+    }
+
+    /**
+     * 更新大题信息
+     *
+     * @param pd
+     * @return
+     */
+    @ApiOperation(value = "更新试卷中的大题", notes = "更新试卷中的大题")
+    @PostMapping(value = "/updatePaperDetail/{paperId}")
+    public ResponseEntity<Object> updatePaperDetail(HttpServletRequest request,
+                                                    @PathVariable String paperId,
+                                                    @RequestBody PaperDetail pd) {
+        User user = getAccessUser();
+        //AccessUser user = (AccessUser) request.getAttribute("accessUser");
+        PaperDetail paperDetail = paperDetailService.savePaperDetail(pd, paperId, user);
+        return new ResponseEntity<>(paperDetail, HttpStatus.OK);
+
+    }
+
+    /**
+     * 新增大题
+     *
+     * @param pd
+     * @return
+     */
+    @ApiOperation(value = "新增大题", notes = "新增大题")
+    @PostMapping(value = "/paperDetail")
+    public ResponseEntity<Object> addPaperDetail(@RequestBody PaperDetail pd) {
+        PaperDetail paperDetail = paperDetailRepo.save(pd);
+        return new ResponseEntity<>(paperDetail, HttpStatus.OK);
+    }
+
+    /**
+     * 删除大题
+     *
+     * @param detailId
+     * @return
+     */
+    @ApiOperation(value = "删除大题", notes = "删除大题")
+    @DeleteMapping(value = "/paperDetail/{detailId}")
+    public ResponseEntity<Object> removePaperDetail(@PathVariable String detailId) {
+        paperDetailService.deletePaperDetail(detailId);
+        return new ResponseEntity<>(detailId, HttpStatus.OK);
+    }
+
+    /**
+     * 根据试卷ID得到所有大题
+     *
+     * @param paperId
+     * @return
+     */
+    @ApiOperation(value = "根据试卷ID得到所有大题", notes = "根据试卷ID得到所有大题")
+    @GetMapping(value = "/paperDetail/paper/{paperId}")
+    public ResponseEntity<Object> getPaperDetailsByPaperId(@PathVariable String paperId) {
+        return new ResponseEntity<>(paperService.findPaperDetailsById(paperId), HttpStatus.OK);
+    }
+
+}

+ 91 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperDetailUnitController.java

@@ -0,0 +1,91 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.service.PaperDetailUnitService;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperDetailUnitExp;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * Created by songyue on 16/12/28.
+ * updated by weiwenhai on 2018.9.28
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class PaperDetailUnitController extends ControllerSupport {
+
+    @Autowired
+    PaperDetailUnitService unitService;
+
+    /**
+     * 获取单个小题对应的试题
+     *
+     * @param unit_id
+     * @return
+     */
+    @ApiOperation(value = "获取小题对应的试题", notes = "获取小题对应的试题")
+    @GetMapping(value = "/paperDetailUnit/question/{unit_id}")
+    public Question getQuestionByUnitId(String unit_id) {
+        return unitService.getQuestionByPaperDetailUnitId(unit_id);
+    }
+
+    /**
+     * 获取小题
+     *
+     * @param unit_id
+     * @return
+     */
+    @ApiOperation(value = "获取小题", notes = "获取小题")
+    @GetMapping(value = "/paperDetailUnit/{unit_id}")
+    public PaperDetailUnit getPaperDetailUnitId(@PathVariable String unit_id) {
+        return unitService.findById(unit_id);
+    }
+
+    /**
+     * 更新小题信息
+     *
+     * @param pdu
+     * @return
+     */
+    @ApiOperation(value = "更新小题", notes = "更新小题")
+    @PutMapping(value = "/paperDetailUnit")
+    public ResponseEntity<Object> updatePaperDetailUnit(@RequestBody PaperDetailUnitExp pdu) {
+        User user = getAccessUser();
+        unitService.savePaperDetailUnit(pdu, user);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 新增小题
+     *
+     * @param pdu
+     * @return
+     */
+    @ApiOperation(value = "新增小题", notes = "新增小题")
+    @PostMapping(value = "/paperDetailUnit")
+    public ResponseEntity<Object> addPaperDetailUnit(@RequestBody PaperDetailUnitExp pdu) {
+        User user = getAccessUser();
+        PaperDetailUnit pdUnit = unitService.savePaperDetailUnit(pdu, user);
+        return new ResponseEntity<>(pdUnit, HttpStatus.OK);
+    }
+
+    /**
+     * 删除小题
+     *
+     * @return
+     */
+    @ApiOperation(value = "删除小题", notes = "删除小题")
+    @DeleteMapping(value = "/paperDetailUnit/{unitId}")
+    public ResponseEntity<Object> removePaperDetailUnit(@PathVariable String unitId) {
+        User user = getAccessUser();
+        unitService.deletePaperDetailUnit(unitId, user);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+}

+ 152 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperStructController.java

@@ -0,0 +1,152 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.PaperStructRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperStruct;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperStructSearchInfo;
+import cn.com.qmth.examcloud.core.questions.service.PaperStructService;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.QuesNameDto;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Created by songyue on 16/12/28.
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class PaperStructController extends ControllerSupport {
+
+    @Autowired
+    PaperStructService paperStructService;
+
+    @Autowired
+    PaperStructRepo paperStructRepo;
+
+    /**
+     * 获取所有试卷结构
+     *
+     * @param
+     * @return
+     */
+    @ApiOperation(value = "获取试卷结构带分页", notes = "获取试卷结构带分页")
+    @GetMapping(value = "/paperStruct/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getPaperStructs(@ModelAttribute PaperStructSearchInfo searchInfo,
+                                                  @PathVariable int curPage,
+                                                  @PathVariable int pageSize) {
+        User user = getAccessUser();
+        if (user != null) {
+            searchInfo.setOrgId(user.getRootOrgId().toString());
+        }
+        Page<PaperStruct> paperStructs = paperStructService.getPaperStructs(searchInfo, curPage, pageSize);
+        return new ResponseEntity<>(paperStructs, HttpStatus.OK);
+    }
+
+    /**
+     * 获取所有试卷结构
+     *
+     * @param
+     * @return
+     */
+    @ApiOperation(value = "获取试卷结构不带分页", notes = "获取试卷结构不带分页")
+    @GetMapping(value = "/paperStruct")
+    public ResponseEntity<Object> getPaperStructs(@ModelAttribute PaperStructSearchInfo searchInfo) {
+        User user = getAccessUser();
+        if (user != null) {
+            searchInfo.setOrgId(user.getRootOrgId().toString());
+        }
+        List<PaperStruct> paperStructs = paperStructService.getPaperStructs(searchInfo);
+        return new ResponseEntity<>(paperStructs, HttpStatus.OK);
+    }
+
+    /**
+     * 根据id获取试卷结构
+     *
+     * @param
+     * @return
+     */
+    @ApiOperation(value = "获取试卷结构", notes = "获取试卷结构")
+    @GetMapping(value = "/paperStruct/{id}")
+    public ResponseEntity<Object> getPaperStructById(@PathVariable String id) {
+        return new ResponseEntity<>(cn.com.qmth.examcloud.core.questions.base.Model.of(paperStructRepo.findById(id)), HttpStatus.OK);
+    }
+
+    /**
+     * 更新试卷结构
+     *
+     * @param
+     * @return
+     */
+    @ApiOperation(value = "更新试卷结构", notes = "更新试卷结构")
+    @PutMapping(value = "/paperStruct")
+    public ResponseEntity<Object> updatePaperStruct(@RequestBody PaperStruct ps) {
+        User user = getAccessUser();
+        PaperStruct paperStruct = paperStructService.save(ps, user);
+        if (paperStruct == null) {
+            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+        } else {
+            return new ResponseEntity<>(paperStruct, HttpStatus.OK);
+        }
+    }
+
+    /**
+     * 新增试卷结构
+     *
+     * @param ps
+     * @return
+     */
+    @ApiOperation(value = "新增试卷结构", notes = "新增试卷结构")
+    @PostMapping(value = "/paperStruct")
+    public ResponseEntity<Object> addPaperStruct(@RequestBody PaperStruct ps) {
+        User user = getAccessUser();
+        PaperStruct paperStructTemp = paperStructService.checkNameUnique(ps.getName(), user.getRootOrgId().toString(), ps.getType());
+        if (paperStructTemp != null) {
+            return new ResponseEntity<>("试卷结构名称重复,请重新命名!", HttpStatus.INTERNAL_SERVER_ERROR);
+        } else {
+            PaperStruct paperStruct = paperStructService.save(ps, user);
+            return new ResponseEntity<>(paperStruct, HttpStatus.OK);
+        }
+    }
+
+    /**
+     * 删除试卷结构
+     *
+     * @param ids
+     * @return
+     */
+    @ApiOperation(value = "删除试卷结构", notes = "删除试卷结构")
+    @DeleteMapping(value = "/paperStruct/{ids}")
+    public ResponseEntity<Object> removePaperStruct(@PathVariable String ids) {
+        List<String> paperList = Stream.of(ids.split(",")).collect(Collectors.toList());
+        paperStructRepo.deleteAll(paperStructRepo.findAllById(paperList));
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 根据题型获取来源大题
+     *
+     * @param
+     * @return
+     */
+    @ApiOperation(value = "获取来源大题", notes = "获取来源大题")
+    @GetMapping(value = "/paperStruct/quesNames")
+    public ResponseEntity<Object> getPaperStructById(@RequestParam(required = false) String courseNo,
+                                                     @RequestParam QuesStructType quesType) {
+        List<QuesNameDto> quesNameDtos = new ArrayList<>();
+        User user = getAccessUser();
+        if (user != null) {
+            quesNameDtos = paperStructService.getQuesNameList(user.getRootOrgId().toString(), courseNo, quesType);
+        }
+        return new ResponseEntity<>(quesNameDtos, HttpStatus.OK);
+    }
+}

+ 132 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PropertyController.java

@@ -0,0 +1,132 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.Constants;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Property;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PropertyDto;
+import cn.com.qmth.examcloud.core.questions.service.PropertyService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author weiwenhai
+ * @describle 属性
+ * @date 2017.11.8
+ */
+@Controller
+@RequestMapping("${api_cqb}/")
+public class PropertyController extends ControllerSupport {
+    @Autowired
+    private PropertyService propertyService;
+
+    @ApiOperation(value = "查询所有属性(树结构)")
+    @GetMapping(value = "/property/all/{coursePropertyId}")
+    public ResponseEntity<Object> findAllProperty(@PathVariable String coursePropertyId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<PropertyDto> properties = propertyService.findParentPropertyDtoList(coursePropertyId, user.getRootOrgId());
+        return new ResponseEntity<>(properties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "新增或更新属性")
+    @RequestMapping(value = "/property/save", method = {RequestMethod.POST, RequestMethod.PUT})
+    public ResponseEntity<Object> saveProperty(@RequestBody PropertyDto property) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        property.setOrgId(user.getRootOrgId());
+        propertyService.saveProperty(property);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "删除属性")
+    @DeleteMapping(value = "/property/delete/{propertyId}/{coursePropertyId}")
+    public ResponseEntity<Object> deleteProperty(@PathVariable String propertyId, @PathVariable String coursePropertyId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        propertyService.deleteProperty(propertyId, coursePropertyId, user.getRootOrgId());
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "查询所有属性")
+    @GetMapping(value = "/property/{coursePropertyId}")
+    public ResponseEntity<Object> findAll(@PathVariable String coursePropertyId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<Property> properties = propertyService.findProperties(coursePropertyId, user.getRootOrgId());
+        return new ResponseEntity<>(properties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据一级查询二级所有属性")
+    @GetMapping(value = "/property/second/{propertyId}")
+    public ResponseEntity<Object> findSons(@PathVariable String propertyId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<Property> properties = propertyService.findSonProperties(propertyId);
+        return new ResponseEntity<>(properties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据课程属性id所有一级属性")
+    @GetMapping(value = "/property/first/{coursePropertyId}")
+    public ResponseEntity<Object> findParents(@PathVariable String coursePropertyId) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        List<Property> properties = propertyService.findParentProperties(coursePropertyId, user.getRootOrgId());
+        return new ResponseEntity<>(properties, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "上移")
+    @PutMapping(value = "/property/moveUp")
+    public ResponseEntity<Object> moveUp(@RequestBody PropertyDto property) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        property.setOrgId(user.getRootOrgId());
+        propertyService.moveUp(property);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "下移")
+    @PutMapping(value = "/property/moveDown")
+    public ResponseEntity<Object> moveDown(@RequestBody PropertyDto property) {
+        User user = getAccessUser();
+        if (user == null) {
+            throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
+        }
+
+        property.setOrgId(user.getRootOrgId());
+        propertyService.moveDown(property);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+}

+ 177 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/QuesController.java

@@ -0,0 +1,177 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import com.google.gson.Gson;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.dao.QuesRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuestionSearchCondition;
+import cn.com.qmth.examcloud.core.questions.service.PaperDetailUnitService;
+import cn.com.qmth.examcloud.core.questions.service.QuesService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * Created by songyue on 16/12/28.
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class QuesController extends ControllerSupport {
+
+    @Autowired
+    QuesService quesService;
+
+    @Autowired
+    QuesRepo quesRepo;
+
+    @Autowired
+    Gson gson;
+    
+    @Autowired
+    PaperDetailUnitService paperDetailUnitService;
+
+    /**
+     * 获取试题
+     *
+     * @param id
+     * @return
+     */
+    @ApiOperation(value = "获取试题", notes = "获取试题")
+    @GetMapping(value = "/question/{id}")
+    public ResponseEntity<Object> getQuesById(@PathVariable String id) {
+        Question ques = cn.com.qmth.examcloud.core.questions.base.Model.of(quesRepo.findById(id));
+        quesService.formatQues(ques);
+        return new ResponseEntity<>(ques, HttpStatus.OK);
+    }
+
+    /**
+     * 分页查询试题
+     *
+     * @return
+     */
+    @ApiOperation(value = "分页查询试题", notes = "分页查询试题")
+    @GetMapping(value = "/question/{curPage}/{pageSize}")
+    public ResponseEntity<Object> getAllQuestion(@ModelAttribute QuestionSearchCondition searchCondition,
+                                                 @PathVariable int curPage,
+                                                 @PathVariable int pageSize) {
+        User user = getAccessUser();
+        searchCondition.setOrgId(user.getRootOrgId().toString());
+        return new ResponseEntity<>(quesService.findAll(searchCondition, curPage, pageSize), HttpStatus.OK);
+    }
+
+    /**
+     * 更新试题信息
+     *
+     * @param
+     * @return
+     */
+    @ApiOperation(value = "更新试题", notes = "更新试题")
+    @PutMapping(value = "/question")
+    public ResponseEntity<Object> updateQuestion(@RequestBody Question question) {
+        quesService.saveQues(question);
+
+        //清除缓存
+        paperDetailUnitService.clearQuestionCache(question.getId());
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 新增试题
+     *
+     * @param question
+     * @return
+     */
+    @ApiOperation(value = "新增试题", notes = "新增试题")
+    @PostMapping(value = "/question")
+    public ResponseEntity<Object> addQuestion(@RequestBody Question question) {
+        return new ResponseEntity<>(quesService.saveQues(question), HttpStatus.CREATED);
+    }
+
+    /**
+     * 删除试题
+     *
+     * @param id
+     * @return
+     */
+    @ApiOperation(value = "删除试题", notes = "删除试题")
+    @DeleteMapping(value = "/question/{id}")
+    public ResponseEntity<Object> removeQuestion(@PathVariable String id) {
+        quesRepo.deleteById(id);
+
+        //清除缓存
+        paperDetailUnitService.clearQuestionCache(id);
+
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+    /**
+     * 删除套题子题
+     *
+     * @param id
+     * @return
+     */
+    @ApiOperation(value = "删除套题子题", notes = "删除套题子题")
+    @PutMapping(value = "/question/{id}/{number}")
+    public ResponseEntity<Object> removeQuestion(@PathVariable String id, @PathVariable String number) {
+        Question question = cn.com.qmth.examcloud.core.questions.base.Model.of(quesRepo.findById(id));
+        List<Question> subQuestions = question.getSubQuestions();
+        subQuestions.remove(Integer.parseInt(number));
+        quesRepo.save(question);
+
+        //清除缓存
+        paperDetailUnitService.clearQuestionCache(id);
+
+        return new ResponseEntity<>(question, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "查询题目数量", notes = "查询题目数量")
+    @GetMapping(value = "/question/count")
+    public ResponseEntity<Object> getQuesCount(@ModelAttribute QuestionSearchCondition searchCondition) {
+        User user = getAccessUser();
+        searchCondition.setOrgId(user.getRootOrgId().toString());
+        long count = quesService.findQuesCount(searchCondition); 
+        return new ResponseEntity<>(count, HttpStatus.OK);
+    }
+
+    @ApiOperation(value = "根据试卷ids更新试题属性", notes = "根据试卷ids更新试题属性")
+    @PutMapping(value = "/question/updatePro/paperIds")
+    public void updateProByPapers(@RequestParam String ids,
+            @RequestParam(required = false) Double difficultyDegree,
+            @RequestParam(required = false) Boolean publicity) {
+        if(difficultyDegree==null&&publicity==null) {
+            throw new StatusException("10001", "难度和公开度不能都为空");
+        }
+        User user = getAccessUser();
+        List<String> paperList = Stream.of(ids.split(",")).collect(Collectors.toList());
+        quesService.updateProByPapers(paperList, difficultyDegree, publicity, user);
+    }
+
+    @ApiOperation(value = "根据课程代码更新试题属性", notes = "根据课程代码更新试题属性")
+    @PutMapping(value = "/question/updatePro/courseCode/{courseCode}/{difficultyDegree}/{publicity}/{orgId}")
+    public ResponseEntity<Object> updateProByCourse(@PathVariable String courseCode,
+                                                    @PathVariable Double difficultyDegree,
+                                                    @PathVariable Boolean publicity,
+                                                    @PathVariable String orgId) {
+        quesService.updateProByCourse(courseCode, difficultyDegree, publicity, orgId);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+
+}

+ 55 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/QuestionAudioController.java

@@ -0,0 +1,55 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuestionAudio;
+import cn.com.qmth.examcloud.core.questions.service.QuestionAudioService;
+import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
+import cn.com.qmth.examcloud.web.support.Naked;
+import io.swagger.annotations.ApiOperation;
+
+/**
+ * @author chenken
+ * @date 2017年8月2日 下午1:49:04
+ * @company QMTH
+ * @description QuestionAudioController.java
+ */
+@RestController
+@RequestMapping("${api_cqb}/")
+public class QuestionAudioController {
+//    @Autowired
+//    private SysProperty sysProperty;
+
+    @Autowired
+    private QuestionAudioService questionAudioService;
+
+    @ApiOperation(value = "通过questionId和fileName获取试题音频文件", notes = "通过questionId和fileName获取试题音频文件")
+    @GetMapping(value = "/questionAudio/{fileName}/{questionId}")
+    public ResponseEntity<Object> findQuestionAudio(@PathVariable String fileName, @PathVariable String questionId) {
+        QuestionAudio questionAudio = questionAudioService.findByQuestionIdAndFileName(questionId, fileName);
+        return new ResponseEntity<>(questionAudio, HttpStatus.OK);
+    }
+
+    @Naked
+    @ApiOperation(value = "通过ID获取试题音频文件", notes = "通过ID获取试题音频文件")
+    @GetMapping(value = "/questionAudio/{questionAudioId}")
+    public ResponseEntity<Object> findQuestionAudioById(@PathVariable String questionAudioId) {
+        QuestionAudio questionAudio = questionAudioService.findAudioById(questionAudioId);
+
+        if (questionAudio != null) {
+            //补全路径
+//            questionAudio.setFileUrl(sysProperty.getDomain() + questionAudio.getFileUrl());
+        	//通用存储
+        	questionAudio.setFileUrl(FileStorageUtil.realPath(questionAudio.getFileUrl()));
+        }
+
+        return new ResponseEntity<>(questionAudio, HttpStatus.OK);
+    }
+
+}

+ 75 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/SettingController.java

@@ -0,0 +1,75 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+
+import cn.com.qmth.examcloud.core.questions.dao.SettingRepo;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.SettingDto;
+import com.google.gson.Gson;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * Created by songyue on 16/12/26.
+ */
+
+@RestController
+@RequestMapping("${api_cqb}/")
+public class SettingController {
+
+    @Autowired
+    Gson gson;
+
+    @Autowired
+    SettingRepo settingRepo;
+
+    /**
+     * 获取全部设置
+     *
+     * @return
+     */
+    @ApiOperation(value = "获取全部设置", notes = "获取全部设置")
+    @GetMapping(value = "/setting")
+    public ResponseEntity<Object> getAllSetting() {
+        return new ResponseEntity<>(settingRepo.findAll(), HttpStatus.OK);
+    }
+
+    /**
+     * 更新设置
+     *
+     * @param settingDto
+     * @return
+     */
+    @ApiOperation(value = "更新设置", notes = "更新设置")
+    @PutMapping(value = "/setting")
+    public ResponseEntity<Object> updateSetting(@RequestBody SettingDto settingDto) {
+        return new ResponseEntity<>(settingRepo.saveAll(settingDto.getSettingList()), HttpStatus.OK);
+    }
+
+    /**
+     * 新增设置
+     *
+     * @param settingDto
+     * @return
+     */
+    @ApiOperation(value = "新增设置", notes = "新增设置")
+    @PostMapping(value = "/setting")
+    public ResponseEntity<Object> addSetting(@RequestBody SettingDto settingDto) {
+        return new ResponseEntity<>(settingRepo.saveAll(settingDto.getSettingList()), HttpStatus.OK);
+    }
+
+    /**
+     * 删除设置
+     *
+     * @param id
+     * @return
+     */
+    @ApiOperation(value = "删除设置", notes = "删除设置")
+    @DeleteMapping(value = "/setting/{id}")
+    public ResponseEntity<Object> removeSetting(@PathVariable String id) {
+        settingRepo.deleteById(id);
+        return new ResponseEntity<>(HttpStatus.OK);
+    }
+
+}

+ 173 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/ExtractConfigCloudServiceProvider.java

@@ -0,0 +1,173 @@
+package cn.com.qmth.examcloud.core.questions.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLog;
+import cn.com.qmth.examcloud.commons.logging.ExamCloudLogFactory;
+import cn.com.qmth.examcloud.core.questions.api.ExtractConfigCloudService;
+import cn.com.qmth.examcloud.core.questions.api.request.*;
+import cn.com.qmth.examcloud.core.questions.api.response.*;
+import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
+import cn.com.qmth.examcloud.core.questions.service.bean.extract.ExtractConfigPaper;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestion;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author weiwenhai
+ * @date 2018.8.15
+ * @company qmth
+ * @describle 调卷规则远程调用请求接口实现类
+ * @code 010
+ */
+@Transactional
+@RestController
+@RequestMapping("${$rmp.cloud.questions}" + "extract_config")
+public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudService {
+
+    protected ExamCloudLog log = ExamCloudLogFactory.getLog(this.getClass());
+
+    private static final long serialVersionUID = 2346453260179681094L;
+
+    @Autowired
+    private ExtractConfigProviderService extractConfigExamService;
+
+    @ApiOperation(value = "根据调卷规则抽取考试试卷")
+    @PostMapping("getPaper")
+    @Override
+    public GetPaperResp getPaper(@RequestBody GetPaperReq paperReq) {
+        if (paperReq.getExamId() == null) {
+            throw new StatusException("400", "examId is null");
+        }
+
+        if (StringUtils.isBlank(paperReq.getCourseCode())) {
+            throw new StatusException("400", "courseCode is empty");
+        }
+
+        if (StringUtils.isBlank(paperReq.getGroupCode())) {
+            throw new StatusException("400", "groupCode is empty");
+        }
+
+        ExtractConfigPaper result = extractConfigExamService.getDefaultPaper(paperReq.getExamId(), paperReq.getCourseCode(), paperReq.getGroupCode());
+
+        GetPaperResp resp = new GetPaperResp();
+        resp.setPaperId(result.getPaperId());
+        resp.setSortQuestionOrder(result.getSortQuestionOrder());
+        resp.setSortOptionOrder(result.getSortOptionOrder());
+        resp.setDefaultPaper(result.getDefaultPaper());
+
+        return resp;
+    }
+
+    @ApiOperation(value = "根据调卷规则抽取考试试题")
+    @PostMapping("getQuestion")
+    @Override
+    public GetQuestionResp getQuestion(@RequestBody GetQuestionReq questionReq) {
+        Long examId = questionReq.getExamId();
+        if (null == examId) {
+            throw new StatusException("Q-010078", "examId is null");
+        }
+        String courseCode = questionReq.getCourseCode();
+        if (StringUtils.isBlank(courseCode)) {
+            throw new StatusException("Q-010082", "courseCode is null");
+        }
+        String groupCode = questionReq.getGroupCode();
+        if (StringUtils.isBlank(groupCode)) {
+            throw new StatusException("Q-010086", "groupCode is null");
+        }
+        String questionId = questionReq.getQuestionId();
+        if (StringUtils.isBlank(questionId)) {
+            throw new StatusException("Q-010090", "questionId is null");
+        }
+        DefaultQuestion defaultQuestion = extractConfigExamService.getDefaultQuestion(examId, courseCode, groupCode, questionId);
+        GetQuestionResp resp = new GetQuestionResp();
+        resp.setDefaultQuestion(defaultQuestion);
+        return resp;
+    }
+
+
+    @ApiOperation(value = "根据试卷id获取试卷结构")
+    @PostMapping("getBasePaper")
+    @Override
+    public GetBasePaperResp getBasePaper(@RequestBody GetBasePaperReq req) {
+        String paperId = req.getPaperId();
+        if (null == paperId) {
+            throw new StatusException("Q-010105", "paperId is null");
+        }
+        DefaultPaper defaultPaper = extractConfigExamService.getBaseDefaultPaper(paperId);
+        GetBasePaperResp resp = new GetBasePaperResp();
+        resp.setDefaultPaper(defaultPaper);
+        return resp;
+    }
+
+    @ApiOperation(value = "根据试题id集合获取试题集合")
+    @PostMapping("getQuestionList")
+    @Override
+    public GetQuestionListResp getQuestionList(@RequestBody GetQuestionListReq req) {
+        Set<String> questionIds = req.getQuestionIds();
+        if (questionIds == null || questionIds.size() < 1) {
+            throw new StatusException("Q-010119", "questionIds is null");
+        }
+        Map<String, DefaultQuestion> map = extractConfigExamService.getDefaultQuestions(questionIds);
+        GetQuestionListResp resp = new GetQuestionListResp();
+        resp.setMap(map);
+        return resp;
+    }
+
+    @Override
+    @ApiOperation(value = "跟试题id查询试题答案")
+    @PostMapping("getQuestionAnswerList")
+    public GetQuestionAnswerResp getQuestionAnswer(GetQuestionAnswerReq req) {
+        if (StringUtils.isBlank(req.getQuestionId())) {
+            throw new StatusException("Q-010135", "questionId is null");
+        }
+        List<String> list = extractConfigExamService.getAnswer(req.getQuestionId());
+        GetQuestionAnswerResp resp = new GetQuestionAnswerResp();
+        resp.setAnswerList(list);
+        return resp;
+    }
+
+    @ApiOperation(value = "根据试题id集合获取试题集合")
+    @PostMapping("getBaseQuestion")
+    @Override
+    public GetQuestionResp getBaseQuestion(@RequestBody GetQuestionReq req) {
+        String id = req.getQuestionId();
+        if (StringUtils.isBlank(id)) {
+            throw new StatusException("Q-010149", "questionId is null");
+        }
+        DefaultQuestion defaultQuestion = extractConfigExamService.getDefaultQuestion(null, null, null, id);
+        GetQuestionResp resp = new GetQuestionResp();
+        resp.setDefaultQuestion(defaultQuestion);
+        return resp;
+    }
+
+    @ApiOperation(value = "根据试卷id查询主观题与客观题数量")
+    @PostMapping("getQuestionStructure")
+    @Override
+    public GetQuestionListResp getQuestionStructure(@RequestBody GetBasePaperReq req) {
+        String paperId = req.getPaperId();
+        if (null == paperId) {
+            throw new StatusException("Q-010163", "paperId is null");
+        }
+        try {
+            String json = extractConfigExamService.getQuestionStructure(paperId);
+            GetQuestionListResp resp = new GetQuestionListResp();
+            resp.setJson(json);
+            return resp;
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+}

+ 233 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/HandleSyncCloudServiceProvider.java

@@ -0,0 +1,233 @@
+package cn.com.qmth.examcloud.core.questions.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
+import cn.com.qmth.examcloud.core.questions.dao.ExtractConfigRepo;
+import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.QuesRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
+import cn.com.qmth.examcloud.core.questions.dao.entity.ExtractConfig;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.global.api.HandleSyncCloudService;
+import cn.com.qmth.examcloud.global.api.request.*;
+import cn.com.qmth.examcloud.global.api.response.*;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.query.Criteria;
+import org.springframework.data.mongodb.core.query.Query;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author weiwenhai
+ * @date 2018.10.11
+ * @company qmth
+ * @description 同步课程
+ * @code 018
+ */
+@Transactional
+@RestController
+@RequestMapping("${$rmp.cloud.questions}" + "dataSync")
+public class HandleSyncCloudServiceProvider extends ControllerSupport implements HandleSyncCloudService {
+
+    private static final long serialVersionUID = -2121791220624238299L;
+
+    @Autowired
+    ExtractConfigRepo extractConfigRepo;
+
+    @Autowired
+    QuesRepo quesRepo;
+
+    @Autowired
+    PaperRepo paperRepo;
+
+    @Autowired
+    MongoTemplate mongoTemplate;
+
+    @ApiOperation(value = "同步课程")
+    @PostMapping("syncCourse")
+    @Override
+    public SyncCourseResp syncCourse(@RequestBody SyncCourseReq req) {
+        String courseCode = req.getCode();
+        Long rootOrgId = req.getRootOrgId();
+        String courseName = req.getName();
+        String courseLevel = req.getLevel();
+        Boolean enable = req.getEnable();
+        Long courseId = req.getId();
+        if (StringUtils.isBlank(courseName)) {
+            throw new StatusException("Q-018073", "courseName is null");
+        }
+        if (null == courseId) {
+            throw new StatusException("Q-018076", "courseId is null");
+        }
+        if (StringUtils.isBlank(courseLevel)) {
+            throw new StatusException("Q-018079", "courseLevel is null");
+        }
+        if (null == rootOrgId) {
+            throw new StatusException("Q-018082", "rootOrgId is null");
+        }
+        //同步调卷规则
+        List<ExtractConfig> extractConfigs = extractConfigRepo.findByCourseCodeAndOrgId(courseCode, rootOrgId.toString());
+        if (extractConfigs != null && extractConfigs.size() > 0) {
+            int i = 0;
+            for (ExtractConfig extractConfig : extractConfigs) {
+                extractConfig.setCourseName(courseName);
+                Course course = extractConfig.getCourse();
+                if (course != null) {
+                    course.setName(courseName);
+                    course.setLevel(courseLevel);
+                    course.setEnable(enable.toString());
+                    course.setUpdateTime(CommonUtils.getCurDateTime());
+                    extractConfig.setCourse(course);
+                } else {
+                    Course tempCourse = new Course();
+                    tempCourse.setCode(courseCode);
+                    tempCourse.setName(courseName);
+                    tempCourse.setLevel(courseLevel);
+                    tempCourse.setEnable(enable.toString());
+                    tempCourse.setOrgId(rootOrgId.toString());
+                    tempCourse.setCreateTime(CommonUtils.getCurDateTime());
+                    extractConfig.setCourse(tempCourse);
+                }
+                i++;
+            }
+            extractConfigRepo.saveAll(extractConfigs);
+            log.debug("调卷规则同步完成,已经同步数量:" + i);
+        }
+        //同步试卷
+        List<Paper> papers = paperRepo.findByCourseNoAndOrgId(courseCode, rootOrgId.toString());
+        if (papers != null && papers.size() > 0) {
+            int i = 0;
+            for (Paper paper : papers) {
+                Course course = paper.getCourse();
+                if (course != null) {
+                    course.setName(courseName);
+                    course.setLevel(courseLevel);
+                    course.setEnable(enable.toString());
+                    course.setUpdateTime(CommonUtils.getCurDateTime());
+                    paper.setCourse(course);
+                } else {
+                    Course tempCourse = new Course();
+                    tempCourse.setCode(courseCode);
+                    tempCourse.setName(courseName);
+                    tempCourse.setLevel(courseLevel);
+                    tempCourse.setEnable(enable.toString());
+                    tempCourse.setOrgId(rootOrgId.toString());
+                    tempCourse.setCreateTime(CommonUtils.getCurDateTime());
+                    paper.setCourse(tempCourse);
+                }
+                i++;
+            }
+            paperRepo.saveAll(papers);
+            log.debug("试卷同步完成,已经同步数量:" + i);
+        }
+        //同步试题
+        Query query = new Query();
+        query.addCriteria(Criteria.where("orgId").is(rootOrgId.toString()));
+        query.addCriteria(Criteria.where("course.code").is(courseCode));
+        List<Question> questions = this.mongoTemplate.find(query, Question.class);
+        if (questions != null && questions.size() > 0) {
+            int i = 0;
+            for (Question question : questions) {
+                Course course = question.getCourse();
+                if (course != null) {
+                    course.setName(courseName);
+                    course.setLevel(courseLevel);
+                    course.setEnable(enable.toString());
+                    course.setUpdateTime(CommonUtils.getCurDateTime());
+                    question.setCourse(course);
+                } else {
+                    Course tempCourse = new Course();
+                    tempCourse.setCode(courseCode);
+                    tempCourse.setName(courseName);
+                    tempCourse.setLevel(courseLevel);
+                    tempCourse.setEnable(enable.toString());
+                    tempCourse.setOrgId(rootOrgId.toString());
+                    tempCourse.setCreateTime(CommonUtils.getCurDateTime());
+                    question.setCourse(tempCourse);
+                }
+                i++;
+            }
+            quesRepo.saveAll(questions);
+            log.debug("试题同步完成,已经同步数量:" + i);
+        }
+        SyncCourseResp resp = new SyncCourseResp();
+        return resp;
+    }
+
+    @Override
+    public SyncOrgResp syncOrg(SyncOrgReq req) {
+        return null;
+    }
+
+    @Override
+    public SyncStudentResp syncStudent(SyncStudentReq req) {
+        return null;
+    }
+
+    @Override
+    public SyncExamStudentResp syncExamStudent(SyncExamStudentReq req) {
+        return null;
+    }
+
+    @Override
+    public SyncSpecialtyResp syncSpecialty(SyncSpecialtyReq req) {
+        return null;
+    }
+
+    @ApiOperation(value = "同步考试")
+    @PostMapping("syncExam")
+    @Override
+    public SyncExamResp syncExam(@RequestBody SyncExamReq req) {
+        Long rootOrgId = req.getRootOrgId();
+        Long id = req.getId();
+        String name = req.getName();
+        String examType = req.getExamType();
+        if (StringUtils.isBlank(name)) {
+            throw new StatusException("Q-018213", "name is null");
+        }
+        if (null == id) {
+            throw new StatusException("Q-018216", "id is null");
+        }
+        if (null == rootOrgId) {
+            throw new StatusException("Q-018219", "rootOrgId is null");
+        }
+        if (StringUtils.isBlank(examType)) {
+            throw new StatusException("Q-018223", "examType is null");
+        }
+        //同步调卷规则
+        Query query = new Query();
+        query.addCriteria(Criteria.where("orgId").is(rootOrgId.toString()));
+        query.addCriteria(Criteria.where("examId").is(id));
+        List<ExtractConfig> extractConfigs = this.mongoTemplate.find(query, ExtractConfig.class);
+        log.debug("调卷规则总数量:" + extractConfigs.size());
+        if (extractConfigs != null && extractConfigs.size() > 0) {
+            int i = 0;
+            for (ExtractConfig extractConfig : extractConfigs) {
+                i++;
+                extractConfig.setExamName(name);
+                extractConfig.setExamType(examType);
+                extractConfig.setExamType(examType);
+            }
+            extractConfigRepo.saveAll(extractConfigs);
+            log.debug("调卷规则同步完成,已经同步数量:" + i);
+        }
+        SyncExamResp resp = new SyncExamResp();
+        return resp;
+    }
+
+    @Override
+    public SyncUserResp syncUser(SyncUserReq req) {
+        return null;
+    }
+
+}

+ 73 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/PaperCloudServiceProvider.java

@@ -0,0 +1,73 @@
+package cn.com.qmth.examcloud.core.questions.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.api.PaperCloudService;
+import cn.com.qmth.examcloud.core.questions.api.request.GetExtractConfigReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetQuestionListReq;
+import cn.com.qmth.examcloud.core.questions.api.response.GetCommonResp;
+import cn.com.qmth.examcloud.core.questions.api.response.GetPaperResp;
+import cn.com.qmth.examcloud.core.questions.service.PaperProviderService;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author weiwenhai
+ * @date 20180.10.10
+ * @company qmth
+ * @description 试卷对象外部接口增删改查请求接口实现类
+ * @code 017
+ */
+
+@Transactional
+@RestController
+@RequestMapping("${$rmp.cloud.questions}" + "default_paper")
+public class PaperCloudServiceProvider implements PaperCloudService {
+
+    private static final long serialVersionUID = -7354673848303599874L;
+
+    @Autowired
+    PaperProviderService paperProviderService;
+
+    @ApiOperation(value = "外部接口组卷", notes = "外部接口组卷")
+    @PostMapping("/genPaper")
+    @Override
+    public GetPaperResp genPaper(@RequestBody GetQuestionListReq req) {
+        if (StringUtils.isBlank(req.getOrgId())) {
+            throw new StatusException("Q-017043", "orgId is null");
+        }
+        if (StringUtils.isBlank(req.getUserName())) {
+            throw new StatusException("Q-017046", "userName is null");
+        }
+        //组卷
+        String paperId = paperProviderService.genPaper(req.getQuestionIds(), req.getMap(), req.getName(), req.getOrgId(), req.getUserName());
+        GetPaperResp resp = new GetPaperResp();
+        resp.setPaperId(paperId);
+        return resp;
+    }
+
+    @ApiOperation(value = "外部接口组卷", notes = "外部接口组卷")
+    @PostMapping("/examPaper")
+    @Override
+    public GetCommonResp examPaper(@RequestBody GetExtractConfigReq req) {
+        if (StringUtils.isBlank(req.getRootOrgId())) {
+            throw new StatusException("Q-017063", "rootOrgId is null");
+        }
+        if (StringUtils.isBlank(req.getPaperId())) {
+            throw new StatusException("Q-017066", "paperId is null");
+        }
+        if (StringUtils.isBlank(req.getExamId() + "")) {
+            throw new StatusException("Q-017069", "examId is null");
+        }
+        String extractConfigId = paperProviderService.examPaper(req.getRootOrgId(), req.getPaperId(), req.getExamId());
+        GetCommonResp resp = new GetCommonResp();
+        resp.setMessage(extractConfigId);
+        return resp;
+    }
+
+}

+ 95 - 0
examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/QuestionCloudServiceProvider.java

@@ -0,0 +1,95 @@
+package cn.com.qmth.examcloud.core.questions.api.provider;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.api.QuestionCloudService;
+import cn.com.qmth.examcloud.core.questions.api.request.GetDefaultQuesionListReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetDefaultQuesionReq;
+import cn.com.qmth.examcloud.core.questions.api.request.GetDefaultQuesionsReq;
+import cn.com.qmth.examcloud.core.questions.api.response.GetCommonResp;
+import cn.com.qmth.examcloud.core.questions.api.response.GetDefaultQuesionIdResp;
+import cn.com.qmth.examcloud.core.questions.api.response.GetDefaultQuestionsResp;
+import cn.com.qmth.examcloud.core.questions.service.QuestionProviderService;
+import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestion;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * @author weiwenhai
+ * @date 20180.9.10
+ * @company qmth
+ * @description 试题对象外部接口增删改查请求接口实现类
+ * @code 011
+ */
+
+@Transactional
+@RestController
+@RequestMapping("${$rmp.cloud.questions}" + "default_question")
+public class QuestionCloudServiceProvider implements QuestionCloudService {
+
+    private static final long serialVersionUID = 263247747318241591L;
+
+    @Autowired
+    QuestionProviderService questionProviderService;
+
+    @ApiOperation(value = "保存试题")
+    @PostMapping("save")
+    @Override
+    public GetDefaultQuesionIdResp saveQuestion(@RequestBody GetDefaultQuesionReq req) {
+        DefaultQuestion defaultQuestion = req.getDefaultQuestion();
+        if (defaultQuestion.getRootOrgId() == null) {
+            throw new StatusException("Q-011028", "rootOrgId is null");
+        }
+        String id = questionProviderService.save(defaultQuestion);
+        GetDefaultQuesionIdResp resp = new GetDefaultQuesionIdResp();
+        resp.setId(id);
+        return resp;
+    }
+
+    @ApiOperation(value = "修改试题")
+    @PostMapping("update")
+    @Override
+    public GetDefaultQuesionIdResp updateQuestion(@RequestBody GetDefaultQuesionReq req) {
+        DefaultQuestion defaultQuestion = req.getDefaultQuestion();
+        if (defaultQuestion.getRootOrgId() == null) {
+            throw new StatusException("Q-011058", "rootOrgId is null");
+        }
+        String id = questionProviderService.save(defaultQuestion);
+        GetDefaultQuesionIdResp resp = new GetDefaultQuesionIdResp();
+        resp.setId(id);
+        return resp;
+    }
+
+    @ApiOperation(value = "查询试题")
+    @PostMapping("find")
+    @Override
+    public GetDefaultQuestionsResp findQuestions(@RequestBody GetDefaultQuesionsReq req) {
+        Long rootOrgId = req.getRootOrgId();
+        if (rootOrgId == null) {
+            throw new StatusException("Q-011058", "rootOrgId is null");
+        }
+        Page<DefaultQuestion> defaultQuestions = questionProviderService.findQustions(rootOrgId, req.getProperties(), req.getCurPage(), req.getPageSize());
+        GetDefaultQuestionsResp resp = new GetDefaultQuestionsResp();
+        resp.setDefaultQuestions(defaultQuestions);
+        return resp;
+    }
+
+    @ApiOperation(value = "保存多个试题")
+    @PostMapping("save_list")
+    @Override
+    public GetCommonResp saveQuestionList(@RequestBody GetDefaultQuesionListReq req) {
+        List<DefaultQuestion> defaultQuestions = req.getDefaultQuestions();
+        String message = questionProviderService.saveList(defaultQuestions);
+        GetCommonResp resp = new GetCommonResp();
+        resp.setMessage(message);
+        return resp;
+    }
+
+}

+ 17 - 0
examcloud-core-questions-api-provider/src/test/java/org/examcloud/core/questions/api/provider/AppTest.java

@@ -0,0 +1,17 @@
+package org.examcloud.core.questions.api.provider;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppTest {
+    private static final Logger log = LoggerFactory.getLogger(AppTest.class);
+
+    @Test
+    public void test() throws Exception {
+        log.debug("into..");
+        Assert.assertTrue(true);
+    }
+
+}

+ 119 - 0
examcloud-core-questions-base/pom.xml

@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<artifactId>examcloud-core-questions</artifactId>
+		<groupId>cn.com.qmth.examcloud.core.questions</groupId>
+		<version>2019-SNAPSHOT</version>
+	</parent>
+	<artifactId>examcloud-core-questions-base</artifactId>
+	<packaging>jar</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-data-mongodb</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-logging</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-web</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud</groupId>
+			<artifactId>examcloud-support</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.question</groupId>
+			<artifactId>examcloud-question-commons</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-global-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-basic-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-examwork-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-print-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+		<dependency>
+			<groupId>cn.com.qmth.examcloud.rpc</groupId>
+			<artifactId>examcloud-core-questions-api-client</artifactId>
+			<version>${examcloud.version}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>com.upyun</groupId>
+			<artifactId>java-sdk</artifactId>
+			<version>3.16</version>
+		</dependency>
+		<dependency>
+			<groupId>org.docx4j</groupId>
+			<artifactId>docx4j-ImportXHTML</artifactId>
+			<version>6.1.0</version>
+			<exclusions>
+				<exclusion>
+					<groupId>org.slf4j</groupId>
+					<artifactId>slf4j-api</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.slf4j</groupId>
+					<artifactId>slf4j-log4j12</artifactId>
+				</exclusion>
+				<exclusion>
+					<groupId>org.slf4j</groupId>
+					<artifactId>jcl-over-slf4j</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>net.sourceforge.jeuclid</groupId>
+			<artifactId>jeuclid-core</artifactId>
+			<version>3.1.9</version>
+			<exclusions>
+				<exclusion>
+					<groupId>org.apache.xmlgraphics</groupId>
+					<artifactId>xmlgraphics-commons</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+		<dependency>
+			<groupId>net.sf.saxon</groupId>
+			<artifactId>Saxon-HE</artifactId>
+			<version>9.9.1-5</version>
+		</dependency>
+		<dependency>
+			<groupId>org.ansj</groupId>
+			<artifactId>ansj_seg</artifactId>
+			<version>5.1.6</version>
+		</dependency>
+		<dependency>
+			<groupId>com.esotericsoftware</groupId>
+			<artifactId>reflectasm</artifactId>
+			<version>1.11.9</version>
+		</dependency>
+
+	</dependencies>
+</project>

+ 125 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/BeanCopierUtil.java

@@ -0,0 +1,125 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import com.esotericsoftware.reflectasm.ConstructorAccess;
+import org.springframework.cglib.beans.BeanCopier;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static java.lang.String.format;
+
+/**
+ * Created by songyue on 17/3/15.
+ */
+public class BeanCopierUtil {
+    private static final Map<String, BeanCopier> beanCopierCache = new ConcurrentHashMap<>();
+    private static final Map<String, ConstructorAccess> constructorAccessCache = new ConcurrentHashMap<>();
+
+    private static void copyProperties(Object source, Object target) {
+        BeanCopier copier = getBeanCopier(source.getClass(), target.getClass());
+        copier.copy(source, target, null);
+    }
+
+    /**
+     * 获取BeanCopier实例
+     *
+     * @param sourceClass
+     * @param targetClass
+     * @return
+     */
+    private static BeanCopier getBeanCopier(Class sourceClass, Class targetClass) {
+        String beanKey = generateKey(sourceClass, targetClass);
+        BeanCopier copier = null;
+        if (!beanCopierCache.containsKey(beanKey)) {
+            copier = BeanCopier.create(sourceClass, targetClass, false);
+            beanCopierCache.put(beanKey, copier);
+        } else {
+            copier = beanCopierCache.get(beanKey);
+        }
+        return copier;
+    }
+
+    /**
+     * 生成key
+     *
+     * @param class1
+     * @param class2
+     * @return
+     */
+    private static String generateKey(Class<?> class1, Class<?> class2) {
+        return class1.toString() + class2.toString();
+    }
+
+    /**
+     * 拷贝对象
+     *
+     * @param source
+     * @param targetClass
+     * @param <T>
+     * @return
+     */
+    public static <T> T copyProperties(Object source, Class<T> targetClass) {
+        T t = null;
+        try {
+            t = targetClass.newInstance();
+        } catch (InstantiationException | IllegalAccessException e) {
+            throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
+        }
+        copyProperties(source, t);
+        return t;
+    }
+
+    /**
+     * 拷贝List
+     *
+     * @param sourceList
+     * @param targetClass
+     * @param <T>
+     * @return
+     */
+    public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) {
+        if (CollectionUtils.isEmpty(sourceList)) {
+            return Collections.emptyList();
+        }
+        ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
+        List<T> resultList = new ArrayList<>(sourceList.size());
+        for (Object o : sourceList) {
+            T t = null;
+            try {
+                t = constructorAccess.newInstance();
+                copyProperties(o, t);
+                resultList.add(t);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return resultList;
+    }
+
+    /**
+     * 获取集合包装类
+     *
+     * @param targetClass
+     * @param <T>
+     * @return
+     */
+    private static <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClass) {
+        ConstructorAccess<T> constructorAccess = constructorAccessCache.get(targetClass.toString());
+        if (constructorAccess != null) {
+            return constructorAccess;
+        }
+        try {
+            constructorAccess = ConstructorAccess.get(targetClass);
+            constructorAccess.newInstance();
+            constructorAccessCache.put(targetClass.toString(), constructorAccess);
+        } catch (Exception e) {
+            throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
+        }
+        return constructorAccess;
+    }
+
+}

+ 67 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/CombinationUtils.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CombinationUtils {
+
+    private static List<String> combineList = new ArrayList<>();
+
+    public static List<Integer> combiantion(List<Integer> numbers, int needNum, int needNestNum) {
+        List<Integer> returnList = new ArrayList<>();
+        if (numbers == null || numbers.size() == 0) {
+            return returnList;
+        }
+
+        List<Integer> list = new ArrayList<>();
+        for (int i = 1; i <= numbers.size(); i++) {
+            combine(numbers, 0, i, list);
+        }
+
+        for (String str : combineList) {
+            returnList = sum(str, needNum, needNestNum);
+            if (returnList.size() > 0) {
+                break;
+            }
+        }
+
+        return returnList;
+    }
+
+    // 从字符数组中第begin个字符开始挑选number个字符加入list中
+    private static void combine(List<Integer> cs, int begin, int number, List<Integer> list) {
+        if (number == 0) {
+            combineList.add(list.toString());
+            return;
+        }
+        if (begin == cs.size()) {
+            return;
+        }
+        list.add(cs.get(begin));
+        combine(cs, begin + 1, number - 1, list);
+        list.remove(cs.get(begin));
+        combine(cs, begin + 1, number, list);
+    }
+
+    private static List<Integer> sum(String str, int needNum, int needNestNum) {
+        List<Integer> list = new ArrayList<>();
+        List<Integer> tempList = new ArrayList<>();
+        String[] strs = str.replaceAll("\\[", "").replaceAll("\\]", "").split(",");
+        Integer sum = 0;
+        if (strs.length == needNestNum) {
+            for (String intStr : strs) {
+                Integer val = Integer.parseInt(intStr.trim());
+
+                sum += val;
+
+                tempList.add(val);
+            }
+            if (sum <= needNum) {
+                list.addAll(tempList);
+            }
+        }
+
+        return list;
+    }
+
+}

+ 847 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/CommonUtils.java

@@ -0,0 +1,847 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.text.translate.*;
+import org.dom4j.*;
+import org.jsoup.Jsoup;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * Created by songyue on 16/12/27.
+ */
+public final class CommonUtils {
+    private static final Logger log = LoggerFactory.getLogger(CommonUtils.class);
+
+    public static final String QUESTION_VERSION = "1.0";
+
+    private static final String COMMON_PROPERTIES = "common.properties";
+
+    private static final String[] CN_SMALL_NUM = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
+
+    private static final String[] CN_BIG_NUM = {"十", "百", "千", "万", "十万", "百万", "千万", "亿", "十亿", "百亿", "千亿"};
+
+    public static final String PAPER_TITLE = "中国石油大学";
+
+    public static final String PAPER_SUB_TITLE = "网络教育";
+
+    private static Random random = new Random();
+
+    private static final String[][] BASIC_ESCAPE = {
+            {"\"", "&quot;"}, // " - double-quote
+            //{"&", "&amp;"},   // & - ampersand
+            {"\"", "&rdquo;"}, // < - less-than
+            {"\u0192", "&fnof;"}, // latin small f with hook = function= florin, U+0192 ISOtech -->
+            // <!-- Greek -->
+            {"\u0391", "&Alpha;"}, // greek capital letter alpha, U+0391 -->
+            {"\u0392", "&Beta;"}, // greek capital letter beta, U+0392 -->
+            {"\u0393", "&Gamma;"}, // greek capital letter gamma,U+0393 ISOgrk3 -->
+            {"\u0394", "&Delta;"}, // greek capital letter delta,U+0394 ISOgrk3 -->
+            {"\u0395", "&Epsilon;"}, // greek capital letter epsilon, U+0395 -->
+            {"\u0396", "&Zeta;"}, // greek capital letter zeta, U+0396 -->
+            {"\u0397", "&Eta;"}, // greek capital letter eta, U+0397 -->
+            {"\u0398", "&Theta;"}, // greek capital letter theta,U+0398 ISOgrk3 -->
+            {"\u0399", "&Iota;"}, // greek capital letter iota, U+0399 -->
+            {"\u039A", "&Kappa;"}, // greek capital letter kappa, U+039A -->
+            {"\u039B", "&Lambda;"}, // greek capital letter lambda,U+039B ISOgrk3 -->
+            {"\u039C", "&Mu;"}, // greek capital letter mu, U+039C -->
+            {"\u039D", "&Nu;"}, // greek capital letter nu, U+039D -->
+            {"\u039E", "&Xi;"}, // greek capital letter xi, U+039E ISOgrk3 -->
+            {"\u039F", "&Omicron;"}, // greek capital letter omicron, U+039F -->
+            {"\u03A0", "&Pi;"}, // greek capital letter pi, U+03A0 ISOgrk3 -->
+            {"\u03A1", "&Rho;"}, // greek capital letter rho, U+03A1 -->
+            // <!-- there is no Sigmaf, and no U+03A2 character either -->
+            {"\u03A3", "&Sigma;"}, // greek capital letter sigma,U+03A3 ISOgrk3 -->
+            {"\u03A4", "&Tau;"}, // greek capital letter tau, U+03A4 -->
+            {"\u03A5", "&Upsilon;"}, // greek capital letter upsilon,U+03A5 ISOgrk3 -->
+            {"\u03A6", "&Phi;"}, // greek capital letter phi,U+03A6 ISOgrk3 -->
+            {"\u03A7", "&Chi;"}, // greek capital letter chi, U+03A7 -->
+            {"\u03A8", "&Psi;"}, // greek capital letter psi,U+03A8 ISOgrk3 -->
+            {"\u03A9", "&Omega;"}, // greek capital letter omega,U+03A9 ISOgrk3 -->
+            {"\u03B1", "&alpha;"}, // greek small letter alpha,U+03B1 ISOgrk3 -->
+            {"\u03B2", "&beta;"}, // greek small letter beta, U+03B2 ISOgrk3 -->
+            {"\u03B3", "&gamma;"}, // greek small letter gamma,U+03B3 ISOgrk3 -->
+            {"\u03B4", "&delta;"}, // greek small letter delta,U+03B4 ISOgrk3 -->
+            {"\u03B5", "&epsilon;"}, // greek small letter epsilon,U+03B5 ISOgrk3 -->
+            {"\u03B6", "&zeta;"}, // greek small letter zeta, U+03B6 ISOgrk3 -->
+            {"\u03B7", "&eta;"}, // greek small letter eta, U+03B7 ISOgrk3 -->
+            {"\u03B8", "&theta;"}, // greek small letter theta,U+03B8 ISOgrk3 -->
+            {"\u03B9", "&iota;"}, // greek small letter iota, U+03B9 ISOgrk3 -->
+            {"\u03BA", "&kappa;"}, // greek small letter kappa,U+03BA ISOgrk3 -->
+            {"\u03BB", "&lambda;"}, // greek small letter lambda,U+03BB ISOgrk3 -->
+            {"\u03BC", "&mu;"}, // greek small letter mu, U+03BC ISOgrk3 -->
+            {"\u03BD", "&nu;"}, // greek small letter nu, U+03BD ISOgrk3 -->
+            {"\u03BE", "&xi;"}, // greek small letter xi, U+03BE ISOgrk3 -->
+            {"\u03BF", "&omicron;"}, // greek small letter omicron, U+03BF NEW -->
+            {"\u03C0", "&pi;"}, // greek small letter pi, U+03C0 ISOgrk3 -->
+            {"\u03C1", "&rho;"}, // greek small letter rho, U+03C1 ISOgrk3 -->
+            {"\u03C2", "&sigmaf;"}, // greek small letter final sigma,U+03C2 ISOgrk3 -->
+            {"\u03C3", "&sigma;"}, // greek small letter sigma,U+03C3 ISOgrk3 -->
+            {"\u03C4", "&tau;"}, // greek small letter tau, U+03C4 ISOgrk3 -->
+            {"\u03C5", "&upsilon;"}, // greek small letter upsilon,U+03C5 ISOgrk3 -->
+            {"\u03C6", "&phi;"}, // greek small letter phi, U+03C6 ISOgrk3 -->
+            {"\u03C7", "&chi;"}, // greek small letter chi, U+03C7 ISOgrk3 -->
+            {"\u03C8", "&psi;"}, // greek small letter psi, U+03C8 ISOgrk3 -->
+            {"\u03C9", "&omega;"}, // greek small letter omega,U+03C9 ISOgrk3 -->
+            {"\u03D1", "&thetasym;"}, // greek small letter theta symbol,U+03D1 NEW -->
+            {"\u03D2", "&upsih;"}, // greek upsilon with hook symbol,U+03D2 NEW -->
+            {"\u03D6", "&piv;"}, // greek pi symbol, U+03D6 ISOgrk3 -->
+            // <!-- General Punctuation -->
+            {"\u2022", "&bull;"}, // bullet = black small circle,U+2022 ISOpub -->
+            // <!-- bullet is NOT the same as bullet operator, U+2219 -->
+            {"\u2026", "&hellip;"}, // horizontal ellipsis = three dot leader,U+2026 ISOpub -->
+            {"\u2032", "&prime;"}, // prime = minutes = feet, U+2032 ISOtech -->
+            {"\u2033", "&Prime;"}, // double prime = seconds = inches,U+2033 ISOtech -->
+            {"\u203E", "&oline;"}, // overline = spacing overscore,U+203E NEW -->
+            {"\u2044", "&frasl;"}, // fraction slash, U+2044 NEW -->
+            // <!-- Letterlike Symbols -->
+            {"\u2118", "&weierp;"}, // script capital P = power set= Weierstrass p, U+2118 ISOamso -->
+            {"\u2111", "&image;"}, // blackletter capital I = imaginary part,U+2111 ISOamso -->
+            {"\u211C", "&real;"}, // blackletter capital R = real part symbol,U+211C ISOamso -->
+            {"\u2122", "&trade;"}, // trade mark sign, U+2122 ISOnum -->
+            {"\u2135", "&alefsym;"}, // alef symbol = first transfinite cardinal,U+2135 NEW -->
+            // <!-- alef symbol is NOT the same as hebrew letter alef,U+05D0 although the
+            // same glyph could be used to depict both characters -->
+            // <!-- Arrows -->
+            {"\u2190", "&larr;"}, // leftwards arrow, U+2190 ISOnum -->
+            {"\u2191", "&uarr;"}, // upwards arrow, U+2191 ISOnum-->
+            {"\u2192", "&rarr;"}, // rightwards arrow, U+2192 ISOnum -->
+            {"\u2193", "&darr;"}, // downwards arrow, U+2193 ISOnum -->
+            {"\u2194", "&harr;"}, // left right arrow, U+2194 ISOamsa -->
+            {"\u21B5", "&crarr;"}, // downwards arrow with corner leftwards= carriage return, U+21B5 NEW -->
+            {"\u21D0", "&lArr;"}, // leftwards double arrow, U+21D0 ISOtech -->
+            // <!-- ISO 10646 does not say that lArr is the same as the 'is implied by'
+            // arrow but also does not have any other character for that function.
+            // So ? lArr canbe used for 'is implied by' as ISOtech suggests -->
+            {"\u21D1", "&uArr;"}, // upwards double arrow, U+21D1 ISOamsa -->
+            {"\u21D2", "&rArr;"}, // rightwards double arrow,U+21D2 ISOtech -->
+            // <!-- ISO 10646 does not say this is the 'implies' character but does not
+            // have another character with this function so ?rArr can be used for
+            // 'implies' as ISOtech suggests -->
+            {"\u21D3", "&dArr;"}, // downwards double arrow, U+21D3 ISOamsa -->
+            {"\u21D4", "&hArr;"}, // left right double arrow,U+21D4 ISOamsa -->
+            // <!-- Mathematical Operators -->
+            {"\u2200", "&forall;"}, // for all, U+2200 ISOtech -->
+            {"\u2202", "&part;"}, // partial differential, U+2202 ISOtech -->
+            {"\u2203", "&exist;"}, // there exists, U+2203 ISOtech -->
+            {"\u2205", "&empty;"}, // empty set = null set = diameter,U+2205 ISOamso -->
+            {"\u2207", "&nabla;"}, // nabla = backward difference,U+2207 ISOtech -->
+            {"\u2208", "&isin;"}, // element of, U+2208 ISOtech -->
+            {"\u2209", "&notin;"}, // not an element of, U+2209 ISOtech -->
+            {"\u220B", "&ni;"}, // contains as member, U+220B ISOtech -->
+            // <!-- should there be a more memorable name than 'ni'? -->
+            {"\u220F", "&prod;"}, // n-ary product = product sign,U+220F ISOamsb -->
+            // <!-- prod is NOT the same character as U+03A0 'greek capital letter pi'
+            // though the same glyph might be used for both -->
+            {"\u2211", "&sum;"}, // n-ary summation, U+2211 ISOamsb -->
+            // <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'
+            // though the same glyph might be used for both -->
+            {"\u2212", "&minus;"}, // minus sign, U+2212 ISOtech -->
+            {"\u2217", "&lowast;"}, // asterisk operator, U+2217 ISOtech -->
+            {"\u221A", "&radic;"}, // square root = radical sign,U+221A ISOtech -->
+            {"\u221D", "&prop;"}, // proportional to, U+221D ISOtech -->
+            {"\u221E", "&infin;"}, // infinity, U+221E ISOtech -->
+            {"\u2220", "&ang;"}, // angle, U+2220 ISOamso -->
+            {"\u2227", "&and;"}, // logical and = wedge, U+2227 ISOtech -->
+            {"\u2228", "&or;"}, // logical or = vee, U+2228 ISOtech -->
+            {"\u2229", "&cap;"}, // intersection = cap, U+2229 ISOtech -->
+            {"\u222A", "&cup;"}, // union = cup, U+222A ISOtech -->
+            {"\u222B", "&int;"}, // integral, U+222B ISOtech -->
+            {"\u2234", "&there4;"}, // therefore, U+2234 ISOtech -->
+            {"\u223C", "&sim;"}, // tilde operator = varies with = similar to,U+223C ISOtech -->
+            // <!-- tilde operator is NOT the same character as the tilde, U+007E,although
+            // the same glyph might be used to represent both -->
+            {"\u2245", "&cong;"}, // approximately equal to, U+2245 ISOtech -->
+            {"\u2248", "&asymp;"}, // almost equal to = asymptotic to,U+2248 ISOamsr -->
+            {"\u2260", "&ne;"}, // not equal to, U+2260 ISOtech -->
+            {"\u2261", "&equiv;"}, // identical to, U+2261 ISOtech -->
+            {"\u2264", "&le;"}, // less-than or equal to, U+2264 ISOtech -->
+            {"\u2265", "&ge;"}, // greater-than or equal to,U+2265 ISOtech -->
+            {"\u2282", "&sub;"}, // subset of, U+2282 ISOtech -->
+            {"\u2283", "&sup;"}, // superset of, U+2283 ISOtech -->
+            // <!-- note that nsup, 'not a superset of, U+2283' is not covered by the
+            // Symbol font encoding and is not included. Should it be, for symmetry?
+            // It is in ISOamsn -->,
+            {"\u2284", "&nsub;"}, // not a subset of, U+2284 ISOamsn -->
+            {"\u2286", "&sube;"}, // subset of or equal to, U+2286 ISOtech -->
+            {"\u2287", "&supe;"}, // superset of or equal to,U+2287 ISOtech -->
+            {"\u2295", "&oplus;"}, // circled plus = direct sum,U+2295 ISOamsb -->
+            {"\u2297", "&otimes;"}, // circled times = vector product,U+2297 ISOamsb -->
+            {"\u22A5", "&perp;"}, // up tack = orthogonal to = perpendicular,U+22A5 ISOtech -->
+            {"\u22C5", "&sdot;"}, // dot operator, U+22C5 ISOamsb -->
+            // <!-- dot operator is NOT the same character as U+00B7 middle dot -->
+            // <!-- Miscellaneous Technical -->
+            {"\u2308", "&lceil;"}, // left ceiling = apl upstile,U+2308 ISOamsc -->
+            {"\u2309", "&rceil;"}, // right ceiling, U+2309 ISOamsc -->
+            {"\u230A", "&lfloor;"}, // left floor = apl downstile,U+230A ISOamsc -->
+            {"\u230B", "&rfloor;"}, // right floor, U+230B ISOamsc -->
+            {"\u2329", "&lang;"}, // left-pointing angle bracket = bra,U+2329 ISOtech -->
+            // <!-- lang is NOT the same character as U+003C 'less than' or U+2039 'single left-pointing angle quotation
+            // mark' -->
+            {"\u232A", "&rang;"}, // right-pointing angle bracket = ket,U+232A ISOtech -->
+            // <!-- rang is NOT the same character as U+003E 'greater than' or U+203A
+            // 'single right-pointing angle quotation mark' -->
+            // <!-- Geometric Shapes -->
+            {"\u25CA", "&loz;"}, // lozenge, U+25CA ISOpub -->
+            // <!-- Miscellaneous Symbols -->
+            {"\u2660", "&spades;"}, // black spade suit, U+2660 ISOpub -->
+            // <!-- black here seems to mean filled as opposed to hollow -->
+            {"\u2663", "&clubs;"}, // black club suit = shamrock,U+2663 ISOpub -->
+            {"\u2665", "&hearts;"}, // black heart suit = valentine,U+2665 ISOpub -->
+            {"\u2666", "&diams;"}, // black diamond suit, U+2666 ISOpub -->
+
+            // <!-- Latin Extended-A -->
+            {"\u0152", "&OElig;"}, // -- latin capital ligature OE,U+0152 ISOlat2 -->
+            {"\u0153", "&oelig;"}, // -- latin small ligature oe, U+0153 ISOlat2 -->
+            // <!-- ligature is a misnomer, this is a separate character in some languages -->
+            {"\u0160", "&Scaron;"}, // -- latin capital letter S with caron,U+0160 ISOlat2 -->
+            {"\u0161", "&scaron;"}, // -- latin small letter s with caron,U+0161 ISOlat2 -->
+            {"\u0178", "&Yuml;"}, // -- latin capital letter Y with diaeresis,U+0178 ISOlat2 -->
+            // <!-- Spacing Modifier Letters -->
+            {"\u02C6", "&circ;"}, // -- modifier letter circumflex accent,U+02C6 ISOpub -->
+            {"\u02DC", "&tilde;"}, // small tilde, U+02DC ISOdia -->
+            // <!-- General Punctuation -->
+            {"\u2002", "&ensp;"}, // en space, U+2002 ISOpub -->
+            {"\u2003", "&emsp;"}, // em space, U+2003 ISOpub -->
+            {"\u2009", "&thinsp;"}, // thin space, U+2009 ISOpub -->
+            {"\u200C", "&zwnj;"}, // zero width non-joiner,U+200C NEW RFC 2070 -->
+            {"\u200D", "&zwj;"}, // zero width joiner, U+200D NEW RFC 2070 -->
+            {"\u200E", "&lrm;"}, // left-to-right mark, U+200E NEW RFC 2070 -->
+            {"\u200F", "&rlm;"}, // right-to-left mark, U+200F NEW RFC 2070 -->
+            {"\u2013", "&ndash;"}, // en dash, U+2013 ISOpub -->
+            {"\u2014", "&mdash;"}, // em dash, U+2014 ISOpub -->
+            {"\u2018", "&lsquo;"}, // left single quotation mark,U+2018 ISOnum -->
+            {"\u2019", "&rsquo;"}, // right single quotation mark,U+2019 ISOnum -->
+            {"\u201A", "&sbquo;"}, // single low-9 quotation mark, U+201A NEW -->
+            {"\u201C", "&ldquo;"}, // left double quotation mark,U+201C ISOnum -->
+            //{"\u201D", "&rdquo;"}, // right double quotation mark,U+201D ISOnum -->
+            {"\u201E", "&bdquo;"}, // double low-9 quotation mark, U+201E NEW -->
+            {"\u2020", "&dagger;"}, // dagger, U+2020 ISOpub -->
+            {"\u2021", "&Dagger;"}, // double dagger, U+2021 ISOpub -->
+            {"\u2030", "&permil;"}, // per mille sign, U+2030 ISOtech -->
+            {"\u2039", "&lsaquo;"}, // single left-pointing angle quotation mark,U+2039 ISO proposed -->
+            // <!-- lsaquo is proposed but not yet ISO standardized -->
+            {"\u203A", "&rsaquo;"}, // single right-pointing angle quotation mark,U+203A ISO proposed -->
+            // <!-- rsaquo is proposed but not yet ISO standardized -->
+            {"\u20AC", "&euro;"}, // -- euro sign, U+20AC NEW -->
+    };
+
+    private static final String[][] BASIC_UNESCAPE = invert(BASIC_ESCAPE);
+
+    /**
+     * 加载properties配置文件
+     *
+     * @param propertiesPath
+     * @return
+     */
+    public static Properties loadProperties(String propertiesPath) {
+        InputStream inputStream = null;
+        Properties properties = new Properties();
+        try {
+            inputStream = ClassLoader.getSystemResourceAsStream(propertiesPath);
+            BufferedReader bf = new BufferedReader(new InputStreamReader(inputStream));
+            properties.load(bf);
+        } catch (Exception e) {
+            log.error("加载配置异常:", e.getMessage());
+        }
+        return properties;
+    }
+
+    /**
+     * 记载通用配置文件
+     *
+     * @return
+     */
+    public static Properties loadCommonProperties() {
+        return loadProperties(COMMON_PROPERTIES);
+    }
+
+    /**
+     * 获取临时文件夹根路径
+     *
+     * @return
+     */
+    public static String getTmpRootPath() {
+        return (String) loadCommonProperties().get("tmp_root_path");
+    }
+
+    /**
+     * 获取当前日期字符串
+     *
+     * @return
+     */
+    public static String getCurDate() {
+        return new SimpleDateFormat("yyyy-MM-dd").format(new Date());
+    }
+
+    /**
+     * 获取当前日期时间字符串
+     *
+     * @return
+     */
+    public static String getCurDateTime() {
+        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
+    }
+
+    /**
+     * 根据当前日期获取数字字符串
+     *
+     * @return
+     */
+    public static String getCurNum() {
+        return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+    }
+
+    /**
+     * 根据枚举名称获取枚举
+     *
+     * @param clazz
+     * @param name
+     * @param <T>
+     * @return
+     */
+    public static <T> T getEnum(Class<T> clazz, String name) {
+        T[] enumConstants = clazz.getEnumConstants();
+        for (T _enum : enumConstants) {
+            if (_enum.toString().equals(name)) {
+                return _enum;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 校验题型
+     *
+     * @param quesType
+     * @return
+     */
+    public static boolean checkQuesType(String quesType) {
+        return Stream.of(QuesStructType.values()).anyMatch(type -> type.getName().equals(quesType));
+    }
+
+    /**
+     * 获取中文数字
+     *
+     * @param inputNum
+     * @return
+     */
+    public static String toCHNum(int inputNum) {
+        String resultNum = "";
+        String numStr = String.valueOf(inputNum);
+
+        int len = numStr.length();
+        for (int i = 0; i < len; i++) {
+            int tmpNum = numStr.charAt(i) - '0';
+            if (i != len - 1 && tmpNum != 0) {
+                resultNum += CN_SMALL_NUM[tmpNum] + CN_BIG_NUM[len - 2 - i];
+            } else {
+                resultNum += CN_SMALL_NUM[tmpNum];
+            }
+        }
+
+        if (resultNum.contains("零")) {
+            resultNum = resultNum.replaceAll("零{1,}", "零");
+        }
+        if (resultNum.endsWith("零")) {
+            resultNum = resultNum.substring(0, resultNum.length() - 1);
+        }
+        if (resultNum.startsWith("一十")) {
+            resultNum = resultNum.replaceFirst("一十", "十");
+        }
+
+        while (StringUtils.countMatches(resultNum, "万") > 1) {
+            resultNum = resultNum.replaceFirst("万", "");
+        }
+
+        while (StringUtils.countMatches(resultNum, "亿") > 1) {
+            resultNum = resultNum.replaceFirst("亿", "");
+        }
+
+        return resultNum;
+    }
+
+    /**
+     * iterator转List
+     *
+     * @param <T>
+     * @param iterator
+     * @return
+     */
+    public static <T> List<T> toList(Iterable<T> iterator) {
+        if (iterator == null) {
+            throw new NullPointerException("Iterator must not be null");
+        }
+        final List<T> list = new ArrayList<T>();
+        final Iterator<T> iter = iterator.iterator();
+        while (iter.hasNext())
+            list.add(iter.next());
+        return list;
+    }
+
+    /**
+     * 将整数转化成字母
+     *
+     * @param number
+     * @return
+     */
+    public static String getOptionNum(int number) {
+        char optionNum = (char) (65 + number);
+        return String.valueOf(optionNum);
+    }
+
+    /**
+     * 字母转换成数字
+     *
+     * @param input
+     * @return
+     */
+    public static Integer characterToNumber(String input) {
+        String reg = "[a-zA-Z]";
+        StringBuffer strBuf = new StringBuffer();
+        input = input.toLowerCase();
+        if (null != input && !"".equals(input)) {
+            for (char c : input.toCharArray()) {
+                if (String.valueOf(c).matches(reg)) {
+                    strBuf.append(c - 96);
+                } else {
+                    strBuf.append(c);
+                }
+            }
+            return Integer.valueOf(strBuf.toString());
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * 从A标签中获取属性值
+     * 例如从:
+     * <a id=\"123456\" name=\"1_1_1.mp3\">
+     * 中获取id的属性值为:123456
+     * 获取name的属性值为:1_1_1.mp3
+     *
+     * @param questionStr
+     * @param attrName
+     * @return
+     */
+    public static String getAttrValue(String questionStr, String attrName) {
+        Pattern aPattern = Pattern.compile("a.*");
+        Matcher aMatcher = aPattern.matcher(questionStr);
+
+        if (aMatcher.find()) {
+            String idRegex = attrName + "=\".*?\"";
+            Pattern idPattern = Pattern.compile(idRegex);
+            Matcher idMatcher = idPattern.matcher(aMatcher.group());
+            if (idMatcher.find()) {
+                return idMatcher.group()
+                        .replaceAll(attrName + "=\"", "")
+                        .replaceAll("\"", "");
+            }
+        }
+
+        return "";
+    }
+
+    /**
+     * 从一段HTML字符串中取出标签中的属性值
+     *
+     * @param htmlString
+     * @return
+     * @throws DocumentException
+     */
+    public static List<String> getAttrValueFromString(String htmlString, String flagName, String attrName) {
+        List<String> idValues = new ArrayList<>();
+        try {
+            Document document = DocumentHelper.parseText(htmlString);
+            Element rootElement = document.getRootElement();
+            List<Element> nodes = new ArrayList<>();
+            if (flagName.equals(rootElement.getName())) {
+                nodes.add(rootElement);
+            } else {
+                nodes = rootElement.elements(flagName);
+            }
+            for (Iterator<Element> itr = nodes.iterator(); itr.hasNext(); ) {
+                Element element = itr.next();
+                Attribute attr = element.attribute(attrName);
+                idValues.add(attr.getValue());
+            }
+        } catch (DocumentException e) {
+            log.error(e.getMessage(), e);
+        }
+        return idValues;
+    }
+
+    /**
+     * 截取a标签中的id的值
+     *
+     * @param htmlString
+     * @return
+     */
+    public static List<String> getTagANames(String htmlString) {
+        List<String> list = new ArrayList<>();
+        Pattern p = Pattern.compile("<a[^<>]*\\s+id=\"([0-9A-Za-z-_.]+)\"\\s*");
+        Matcher m = p.matcher(htmlString);
+        while (m.find()) {
+            String str = m.group(1);
+            list.add(str);
+        }
+        return list;
+    }
+
+    /**
+     * 截取a标签中的id的值
+     *
+     * @param htmlString
+     * @return
+     */
+    public static List<String> getTagANames2(String htmlString) {
+        List<String> list = new ArrayList<>();
+        Pattern p = Pattern.compile("<a[^<>]*\\s+name=\"([0-9A-Za-z-_.]+)\"\\s*");
+        Matcher m = p.matcher(htmlString);
+        while (m.find()) {
+            String str = m.group(1);
+            list.add(str);
+        }
+        return list;
+    }
+
+    /**
+     * 保留两位小数
+     *
+     * @param number
+     * @return
+     */
+    public static double formatDouble(double number) {
+        BigDecimal formatNumber = BigDecimal.valueOf(number);
+        return formatNumber.setScale(2, RoundingMode.HALF_UP).doubleValue();
+    }
+
+    /**
+     * 保留一位小数点
+     */
+    public static String formatValue(Double score) {
+        if (score == null) {
+            return "";
+        }
+        DecimalFormat decimalFormat = new DecimalFormat("#.#");
+        return decimalFormat.format(score);
+    }
+
+    /**
+     * 去掉包含“.0”的小数点
+     */
+    public static String clearZeroPoint(String scoreStr) {
+        if (scoreStr != null && scoreStr.indexOf(".0") > -1) {
+            return scoreStr.replace(".0", "");
+        }
+        return scoreStr;
+    }
+
+    /**
+     * 向下取整,可以取0.5
+     *
+     * @param number
+     * @return
+     */
+    public static double formatDoubleFloor(double number) {
+        BigDecimal formatNumber = BigDecimal.valueOf(number);
+        double floorNumber = formatNumber.setScale(0, RoundingMode.FLOOR).doubleValue();
+        if (number >= floorNumber && number < floorNumber + 0.5) {
+            return floorNumber;
+        } else {
+            return floorNumber + 0.5;
+        }
+    }
+
+    /**
+     * 补全html标签
+     *
+     * @param htmlStr
+     * @return
+     * @throws Exception
+     */
+    public static String repairHtmlStr(String htmlStr) throws Exception {
+        htmlStr = htmlStr.trim();
+        if (htmlStr.toLowerCase().contains("<!doctype html ")) {
+            int index1 = htmlStr.toLowerCase().indexOf("<!doctype html ");
+            int index2 = htmlStr.indexOf('>', index1 + 1);
+            htmlStr = htmlStr.substring(0, index1) + htmlStr.substring(index2 + 1);
+        }
+        while (htmlStr.toLowerCase().contains("<br ")) {
+            int index1 = htmlStr.toLowerCase().indexOf("<br ");
+            int index2 = htmlStr.toLowerCase().indexOf('>', index1 + 1);
+            htmlStr = htmlStr.substring(0, index1) + "<br/>" + htmlStr.substring(index2 + 1);
+        }
+        while (htmlStr.toLowerCase().endsWith("<br>") || htmlStr.toLowerCase().endsWith("<br/>")) {
+            if (htmlStr.toLowerCase().endsWith("<br>")) {
+                htmlStr = htmlStr.substring(0, htmlStr.length() - "<br>".length());
+            } else if (htmlStr.toLowerCase().endsWith("<br/>")) {
+                htmlStr = htmlStr.substring(0, htmlStr.length() - "<br/>".length());
+            }
+        }
+        htmlStr = htmlStr.replace("<br>", "<br/>").replace("<BR>", "<br/>");
+
+        {//补全META标签
+            int imgIndex = indexOfRegex(htmlStr, "<((meta)|(META)) ");
+            while (imgIndex > 0) {
+                int flag = htmlStr.indexOf('>', imgIndex);
+                if (htmlStr.charAt(flag - 1) != '/') {
+                    htmlStr = htmlStr.substring(0, flag) + "/" + htmlStr.substring(flag);
+                }
+                imgIndex = indexOfRegex(htmlStr, "<((meta)|(META)) ", flag);
+            }
+        }
+
+        {//补全img标签
+            int imgIndex = indexOfRegex(htmlStr, "<((img)|(IMG)) ");
+            while (imgIndex > 0) {
+                int flag = htmlStr.indexOf('>', imgIndex);
+                if (htmlStr.charAt(flag - 1) != '/') {
+                    htmlStr = htmlStr.substring(0, flag) + "/" + htmlStr.substring(flag);
+                }
+                imgIndex = indexOfRegex(htmlStr, "<((img)|(IMG)) ", flag);
+            }
+        }
+        //添加body标签
+        if ((htmlStr.toLowerCase().contains("<p") || htmlStr.toLowerCase().contains("<span"))
+                && !htmlStr.toLowerCase().contains("<body")) {
+            htmlStr = "<body>" + htmlStr + "</body>";
+        }
+        return new String(htmlStr.getBytes("UTF-8"));
+    }
+
+    /**
+     * 从指定的位置开始查找第一个匹配正则表达式的字符串的位置
+     *
+     * @param str
+     * @param regex     正则表达式
+     * @param fromIndex 指定的起始位置
+     * @return
+     */
+    public static int indexOfRegex(String str, String regex, int fromIndex) {
+        int index = indexOfRegex(str.substring(fromIndex), regex);
+        if (index < 0) {
+            return -1;
+        }
+        return fromIndex + index;
+    }
+
+    /**
+     * 查找第一个匹配正则表达式的字符串的位置
+     *
+     * @param str
+     * @param regex 正则表达式
+     * @return
+     */
+    public static int indexOfRegex(String str, String regex) {
+        Pattern p = Pattern.compile(regex);
+        Matcher m = p.matcher(str);
+        if (m.find()) {
+            return m.start();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * 格式化html
+     *
+     * @param htmlStr
+     * @return
+     * @throws Exception
+     */
+    public static String formatHtml(String htmlStr) throws Exception {
+        if (StringUtils.isEmpty(htmlStr)) {
+            return "";
+        }
+        htmlStr = repairHtmlStr(htmlStr);
+        //htmlStr = StringEscapeUtils.unescapeHtml4(htmlStr);
+        htmlStr = UNESCAPE_HTML4.translate(htmlStr);
+        return htmlStr;
+    }
+
+    /**
+     * 过滤非空格的空字符
+     *
+     * @param str
+     * @return
+     */
+    public static String trimNoBlankSpace(final String str) {
+        if (str == null || str.length() == 0) {
+            return "";
+        }
+        return str.replaceAll("[\\t\\r\\f]*", "");
+    }
+
+    /**
+     * 替换掉字符串的Unicode字符为中文字符
+     * 如下所示
+     * \u0026ldquo;=“
+     * \u0026mdash;=—
+     * \u0026lt;=<
+     * \u0026hellip;=…
+     * \u0026rdquo;=”
+     * \u0026gt;=>
+     */
+    public static String replaceUnicodeStr(String content) {
+        try {
+            Pattern pattern = Pattern.compile("\\\\u[a-f0-9A-F]{1,4}");
+            Matcher matcher = pattern.matcher(content);
+            Map<String, String> unicodeMap = new HashMap<>();
+            while (matcher.find()) {
+                String unicodeStr = matcher.group();
+                byte[] unicodeStrByte = unicodeStr.getBytes();
+                String resultByte = new String(unicodeStrByte, "utf-8");
+                String escapeStr = StringEscapeUtils.unescapeHtml4(StringEscapeUtils.unescapeEcmaScript(resultByte));
+                unicodeMap.put(unicodeStr, escapeStr);
+            }
+
+            if (unicodeMap.size() > 0) {
+                for (String key : unicodeMap.keySet()) {
+                    //replace函数替换时忽略正则表达式符号,replaceAll和replaceFirst函数替换时是使用正则表达式匹配的。
+                    content = content.replace(key, unicodeMap.get(key));
+                }
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return content;
+    }
+
+    /**
+     * weiwenhai
+     * 过滤题干中<>,不让反转
+     */
+    public static final CharSequenceTranslator UNESCAPE_HTML4 =
+            new AggregateTranslator(
+                    new LookupTranslator(BASIC_UNESCAPE),
+                    new LookupTranslator(EntityArrays.ISO8859_1_UNESCAPE()),
+                    new NumericEntityUnescaper()
+            );
+
+    public static String[][] invert(final String[][] array) {
+        final String[][] newarray = new String[array.length][2];
+        for (int i = 0; i < array.length; i++) {
+            newarray[i][0] = array[i][1];
+            newarray[i][1] = array[i][0];
+        }
+        return newarray;
+    }
+
+    /**
+     * 删除html中p标签
+     *
+     * @param htmlStr
+     * @return
+     */
+    public static String deleteHtmlP(String htmlStr) {
+        htmlStr = htmlStr.replace("<p>", "").replace("</p>", "");
+        return htmlStr;
+    }
+
+    /**
+     * 生成不同的随机整数集合
+     *
+     * @param list
+     * @param size     数量
+     * @param original 随机数范围
+     * @return
+     */
+    public static List<Integer> getRandom(List<Integer> list, int size, int original) {
+        Integer i = random.nextInt(original);
+        if (!list.contains(i) && i < original) {
+            list.add(i);
+            if (list.size() == size) {
+                return list;
+            }
+        }
+        getRandom(list, size, original);
+        return list;
+    }
+
+    /**
+     * 判断字符串是否为正数
+     *
+     * @param str
+     * @return
+     */
+    public static boolean isInteger(String str) {
+        Boolean strResult = str.matches("-?[0-9]+.?[0-9]*");
+        try {
+            if (strResult && Double.parseDouble(str) > 0) {
+                return true;
+            }
+        } catch (NumberFormatException ex) {
+            return false;
+        }
+        return false;
+    }
+
+    public static String relaceQuestionIdx(String str, int baseIdx) {
+        StringBuffer sb = new StringBuffer("");
+        Pattern pattern = Pattern.compile("##(\\d+)##");
+
+        Matcher matcher = pattern.matcher(str);
+
+        while (matcher.find()) {
+            String idx = matcher.group(1);
+            matcher.appendReplacement(sb, "___" + String.valueOf(Integer.parseInt(idx) + baseIdx) + "___");
+        }
+
+        if (StringUtils.isEmpty(sb.toString())) {
+            return str;
+        } else {
+            matcher.appendTail(sb);
+            return sb.toString();
+        }
+    }
+
+    /**
+     * 去除字符串中所有的html标签
+     *
+     * @param html
+     * @return
+     */
+    public static String parseHtml(String html) {
+        if (StringUtils.isEmpty(html)) {
+            return "";
+        }
+        org.jsoup.nodes.Document document = Jsoup.parse(html);
+        return document.text();
+    }
+
+    /**
+     * 正则表达是匹配特殊字符进行转义
+     *
+     * @param keyword
+     * @return
+     */
+    public static String escapeExprSpecialWord(String keyword) {
+        if (StringUtils.isNotBlank(keyword)) {
+            String[] fbsArr = {"\\", "$", "(", ")", "*", "+", ".", "[", "]", "?", "^", "{", "}", "|"};
+            for (String key : fbsArr) {
+                if (keyword.contains(key)) {
+                    keyword = keyword.replace(key, "\\" + key);
+                    return keyword;
+                }
+            }
+        }
+        return keyword;
+    }
+}

+ 28 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/Constants.java

@@ -0,0 +1,28 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-10-17 15:18:02.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base;
+
+/**
+ * 系统常量
+ *
+ * @author: fengdesheng
+ * @since: 2018/10/17
+ */
+public interface Constants {
+
+    /**
+     * 系统错误
+     */
+    String SYS_CODE_500 = "000500";
+
+    /**
+     * 参数错误
+     */
+    String SYS_CODE_400 = "000400";
+
+}

+ 369 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/FileDisposeUtil.java

@@ -0,0 +1,369 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.commons.io.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author chenken
+ * @date 2017年7月17日 上午9:36:32
+ * @company QMTH
+ */
+public class FileDisposeUtil {
+
+    private static final Logger logger = LoggerFactory.getLogger(FileDisposeUtil.class);
+
+    /**
+     * 将网络文件保存到本地
+     *
+     * @param fileUrl
+     *            网络文件URL
+     * @param localFilePath
+     *            例如D:/123.txt
+     * @return
+     */
+    public static boolean saveUrlAs(String fileUrl, String localFilePath) {
+        URL url;
+        try {
+            url = new URL(fileUrl);
+        } catch (MalformedURLException e) {
+            logger.error("fileUrl:"+fileUrl, e);
+            return false;
+        }
+
+        HttpURLConnection connection;
+        try {
+            connection = (HttpURLConnection) url.openConnection();
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+            return false;
+        }
+
+        try (DataInputStream dataInputStream = new DataInputStream(connection.getInputStream());
+                FileOutputStream fileOutputStream = new FileOutputStream(localFilePath);
+                DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);) {
+
+            byte[] buffer = new byte[4096];
+            int count;
+            while ((count = dataInputStream.read(buffer)) > 0) {
+                dataOutputStream.write(buffer, 0, count);
+            }
+            fileOutputStream.flush();
+            dataOutputStream.flush();
+            return true;
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+            return false;
+        } finally {
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    /**
+     * 下载服务器上的文件
+     *
+     * @param filename
+     *            文件名称
+     * @param fullFilePath
+     *            文件全路径
+     * @param response
+     */
+    public static void downloadFile(String filename, String fullFilePath, HttpServletResponse response) {
+        try (InputStream in = new FileInputStream(fullFilePath); OutputStream out = response.getOutputStream();) {
+
+            // 设置编码
+            response.setCharacterEncoding("UTF-8");
+
+            // 设置文件MIME类型
+            response.setContentType(getContentType(filename));
+
+            // 设置Content-Disposition,名称强制为UTF-8
+            response.setHeader("Content-Disposition", "attachment;filename="
+                    + URLEncoder.encode(filename, "UTF-8").replace("%28", "(").replace("%29", ")"));
+            response.setHeader("Accept-Length", String.valueOf(in.available()));
+
+            // 设置强制下载不打开
+            response.setContentType("application/octet-stream;charset=utf-8");
+
+            // 读取目标文件,通过response将目标文件写到客户端
+            byte[] buffer = new byte[4096];
+            int count;
+            while ((count = in.read(buffer)) > 0) {
+                out.write(buffer, 0, count);
+            }
+
+            response.flushBuffer();
+        } catch (FileNotFoundException e) {
+            logger.error(e.getMessage(), e);
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 获得文件MIME类型
+     *
+     * @param filename
+     * @return
+     */
+    public static String getContentType(String filename) {
+        String type = null;
+        Path path = Paths.get(filename);
+        try {
+            type = Files.probeContentType(path);
+        } catch (IOException e) {
+            logger.error(e.getMessage(), e);
+        }
+        return type;
+    }
+
+    /**
+     * 将存放在sourceFilePath目录下的源文件,打包成fileName名称的zip文件,并存放到zipFilePath路径下
+     *
+     * @param sourceFilePath
+     *            :待压缩的文件夹路径
+     * @param zipFilePath
+     *            :压缩后zip文件的存放路径
+     * @param fileName
+     *            :zip文件的名称
+     * @return
+     */
+    public static boolean fileToZip(String sourceFilePath, String zipFilePath, String fileName) {
+        logger.info("压缩" + sourceFilePath + "目录开始");
+
+        File sourceFile = new File(sourceFilePath);
+        if (!sourceFile.exists()) {
+            logger.error("待压缩的文件目录:" + sourceFilePath + "不存在.");
+            return false;
+        }
+
+        File zipFile = new File(zipFilePath + File.separator + fileName + ".zip");
+        if (zipFile.exists()) {
+            logger.error(zipFilePath + "目录下存在名字为:" + fileName + ".zip" + "打包文件.");
+            return false;
+        }
+
+        File[] sourceFiles = sourceFile.listFiles();
+        if (null == sourceFiles || sourceFiles.length < 1) {
+            logger.error("待压缩的文件目录:" + sourceFilePath + "里面不存在文件,无需压缩.");
+            return false;
+        }
+
+        try (FileOutputStream fos = new FileOutputStream(zipFile);
+                BufferedOutputStream bos = new BufferedOutputStream(fos);
+                ZipOutputStream zos = new ZipOutputStream(bos);) {
+
+            byte[] bytes = new byte[1024 * 10];
+            for (int i = 0; i < sourceFiles.length; i++) {
+                File file = sourceFiles[i];
+                if (!file.isFile()) {
+                    continue;
+                }
+
+                try (FileInputStream fis = new FileInputStream(file);
+                        BufferedInputStream bis = new BufferedInputStream(fis, 1024 * 10);) {
+                    // 创建ZIP实体,并添加进压缩包
+                    String fileEncode = System.getProperty("file.encoding");
+                    String name = new String(file.getName().getBytes(fileEncode), "UTF-8");
+
+                    ZipEntry zipEntry = new ZipEntry(name);
+                    zos.putNextEntry(zipEntry);
+
+                    // 读取待压缩的文件并写进压缩包里
+                    int read;
+                    while ((read = bis.read(bytes, 0, 1024 * 10)) != -1) {
+                        zos.write(bytes, 0, read);
+                    }
+
+                    zos.flush();
+                } catch (Exception e) {
+                    logger.error(e.getMessage(), e);
+                }
+            }
+
+            logger.info("压缩" + sourceFilePath + "目录完成");
+            return true;
+        } catch (Exception e) {
+            logger.error(e.getMessage(), e);
+        }
+
+        return false;
+    }
+
+    public static void createDirectory(String downloadDirectory) {
+        File directory = new File(downloadDirectory);
+        if (!directory.exists()) {
+            directory.mkdirs();
+        } else {
+            FileUtils.deleteQuietly(directory);
+            directory.mkdirs();
+        }
+    }
+
+    public static File createZip(String sourceFilePath, String targetFilePath) throws IOException {
+        OutputStream fos = null;
+        ZipOutputStream zos = null;
+        try {
+            File zipfile = new File(targetFilePath);
+            zipfile.deleteOnExit();
+            fos = new FileOutputStream(zipfile);
+            zos = new ZipOutputStream(fos);
+            // zos.setEncoding("utf-8"); // Solve linxu's mess
+            writeZip(new File(sourceFilePath), null, zos);
+            return zipfile;
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+            if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    private static void writeZip(File file, String parentPath, ZipOutputStream zos) throws IOException {
+        if (file.exists()) {
+            ZipEntry ze = null;
+            if (file.isDirectory()) {// Processing folder
+                if (parentPath == null) {
+                    parentPath = "";
+                } else {
+                    parentPath += file.getName() + File.separator;
+                }
+                File[] files = file.listFiles();
+                if (files != null) {
+                    for (File f : files) {
+                        writeZip(f, parentPath, zos);
+                    }
+                } else { // An empty directory creates the current directory
+                    try {
+                        ze = new ZipEntry(parentPath);
+                        // ze.setUnixMode(755);// Solve Linux mess file Settings
+                        // 644 directory Settings 755
+                        zos.putNextEntry(ze);
+
+                        zos.flush();
+                    } finally {
+                        if (zos != null) {
+                            zos.closeEntry();
+                        }
+                    }
+                }
+            } else {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(file);
+                    ze = new ZipEntry(parentPath + file.getName());
+                    // ze.setUnixMode(644);// Solve Linux mess file Settings 644
+                    // directory Settings 755
+                    zos.putNextEntry(ze);
+                    byte[] content = new byte[1024];
+                    int len;
+                    while ((len = fis.read(content)) != -1) {
+                        zos.write(content, 0, len);
+                        zos.flush();
+                    }
+
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                    if (zos != null) {
+                        zos.closeEntry();
+                    }
+                }
+            }
+        }
+    }
+
+    public static void unZipFiles(String zipFileName, String targetDirName) throws IOException {
+        if (!targetDirName.endsWith(File.separator)) {
+            targetDirName = targetDirName + File.separator;
+        }
+        ZipFile zipFile = null;
+        try {
+            // Create the ZipFile object from the ZIP file
+            zipFile = new ZipFile(zipFileName);
+            ZipEntry entry = null;
+            String entryName = null;
+            String descFileDir = null;
+            byte[] buf = new byte[4096];
+            int readByte = 0;
+            // Gets all entry in the ZIP file
+            @SuppressWarnings("rawtypes")
+            Enumeration enums = zipFile.entries();
+            // Go through all entry
+            while (enums.hasMoreElements()) {
+                entry = (ZipEntry) enums.nextElement();
+                // Get the name entry
+                entryName = entry.getName();
+                descFileDir = targetDirName + entryName;
+                if (entry.isDirectory()) {
+                    // If entry is a directory, create the directory
+                    // entry.setUnixMode(755);// Solve Linux mess file Settings
+                    // 644 directory Settings 755
+                    new File(descFileDir).mkdirs();
+                    continue;
+                } else {
+                    // If entry is a file, the parent directory is created
+                    // entry.setUnixMode(644);//Solve Linux mess file Settings
+                    // 644 directory Settings 755
+                    new File(descFileDir).getParentFile().mkdirs();
+                }
+                File file = new File(descFileDir);
+                // Open the file output stream
+                OutputStream os = null;
+                // Open the entry input stream from the ZipFile object
+                InputStream is = null;
+                try {
+                    os = new FileOutputStream(file);
+                    is = zipFile.getInputStream(entry);
+                    while ((readByte = is.read(buf)) != -1) {
+                        os.write(buf, 0, readByte);
+                    }
+                } finally {
+                    if (os != null)
+                        os.close();
+                    if (is != null)
+                        is.close();
+                }
+            }
+        } finally {
+            if (zipFile != null)
+                zipFile.close();
+        }
+    }
+
+    public static String getDocxBasePath() {
+        String path = FileDisposeUtil.class.getResource("/").getPath() + "templates/docx";
+        return path;
+    }
+
+}

+ 44 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/GridFSUtil.java

@@ -0,0 +1,44 @@
+//package cn.com.qmth.examcloud.core.questions.base;
+//
+//import com.mongodb.BasicDBObject;
+//import com.mongodb.DBObject;
+//import com.mongodb.gridfs.GridFSDBFile;
+//import com.mongodb.gridfs.GridFSFile;
+//import org.bson.types.ObjectId;
+//import org.springframework.beans.factory.annotation.Autowired;
+//import org.springframework.data.mongodb.core.query.Criteria;
+//import org.springframework.data.mongodb.core.query.Query;
+//import org.springframework.data.mongodb.gridfs.GridFsOperations;
+//import org.springframework.stereotype.Service;
+//import java.util.List;
+//
+//import java.io.InputStream;
+//
+///**
+// * Created by songyue on 17/2/27.
+// */
+//@Service
+//public final class GridFSUtil {
+//
+//    @Autowired
+//    private GridFsOperations gridOperation;
+//
+//    public String save(InputStream inputStream, String fileName) {
+//        DBObject metaData = new BasicDBObject();
+//        metaData.put("fileName", fileName);
+//        GridFSFile file = gridOperation.store(inputStream, fileName, metaData);
+//        return file.getId().toString();
+//    }
+//
+//    public GridFSDBFile get(String id) {
+//        return gridOperation.findOne(new Query(Criteria.where("_id").is(new ObjectId(id))));
+//    }
+//
+//    public List listFiles() {
+//        return gridOperation.find(null);
+//    }
+//
+//    public GridFSDBFile getByFileName(String fileName) {
+//        return gridOperation.findOne(new Query(Criteria.where("filename").is(fileName)));
+//    }
+//}

+ 11 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/IdUtils.java

@@ -0,0 +1,11 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import java.util.UUID;
+
+public class IdUtils {
+
+    public static String uuid() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+}

+ 143 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/ImageUtils.java

@@ -0,0 +1,143 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-06-19 14:24:38.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base;
+
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.select.Elements;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ImageUtils {
+    private static final Logger log = LoggerFactory.getLogger(ImageUtils.class);
+    private static final String TAG_IMG = "img";
+    private static final String UNIT = "px";
+    private static final int MAX_WIDTH = 350;
+    private static final String STYLE_WIDTH = "width:%spx;";
+    private static final String STYLE_HEIGHT = "height:%spx;";
+    public static final String ATTR_WIDTH = "width";
+    public static final String ATTR_HEIGHT = "height";
+
+    /**
+     * 如果图片尺寸过大,则按比例缩放图片的宽度高度
+     */
+    public static String reSizeImg(String html) {
+        if (html == null) {
+            return "";
+        }
+        Document document = Jsoup.parse(html);
+        Elements elements = document.getElementsByTag(TAG_IMG);
+        for (Element element : elements) {
+            int width = ImageUtils.parseValue(element.attr("style"), "width");
+            int height = ImageUtils.parseValue(element.attr("style"), "height");
+            //log.debug(width + " - " + height);
+            element.removeAttr("width");
+            element.removeAttr("height");
+            String widthStr = "";
+            String heightStr = "";
+            boolean isOutWidth = false;
+            if (width > 0) {
+                if (width > MAX_WIDTH) {
+                    isOutWidth = true;
+                    widthStr = String.format(STYLE_WIDTH, MAX_WIDTH);
+                } else {
+                    widthStr = String.format(STYLE_WIDTH, width);
+                }
+            }
+            if (height > 0) {
+                if (isOutWidth) {
+                    heightStr = String.format(STYLE_HEIGHT, calculateHeight(width, height, MAX_WIDTH));
+                } else {
+                    heightStr = String.format(STYLE_HEIGHT, height);
+                }
+            }
+            element.attr("style", widthStr + heightStr);
+        }
+        String bodyHtml = document.body().html();
+        //log.debug(bodyHtml);
+        return bodyHtml;
+    }
+
+    public static int calculateHeight(int oldWidth, int oldHeight, int newWidth) {
+        if (oldWidth == 0) {
+            return 0;
+        }
+        int newHeight = (newWidth * oldHeight) / oldWidth;
+        return newHeight;
+    }
+
+    public static int parseValue(String styleValue, String attrName) {
+        if (styleValue == null || "".equals(styleValue)) {
+            return 0;
+        }
+        try {
+            styleValue = styleValue.replaceAll(" ", "");
+            String[] attrs = styleValue.toLowerCase().split(";");
+            for (String attr : attrs) {
+                if (!attr.startsWith(attrName)) {
+                    continue;
+                }
+                String[] values = attr.split(":");
+                if (values.length > 1) {
+                    String value = values[1].toLowerCase().replaceAll(UNIT, "");
+                    return Integer.parseInt(value);
+                }
+            }
+        } catch (Exception e) {
+            //do nothing
+        }
+        return 0;
+    }
+
+    public static Map<String, Integer> parseImageWidthAndHeight(String imgTagContent) {
+        Map<String, Integer> result = new HashMap<>();
+        if (imgTagContent == null) {
+            return result;
+        }
+
+        Document document = Jsoup.parse(imgTagContent);
+        Elements elements = document.getElementsByTag(TAG_IMG);
+        if (elements == null || elements.isEmpty()) {
+            return result;
+        }
+
+        //只处理第一个图片元素
+        Element element = elements.first();
+        String widthContent = element.attr(ATTR_WIDTH);
+        String heightContent = element.attr(ATTR_HEIGHT);
+        result.put(ATTR_WIDTH, parseValue(widthContent));
+        result.put(ATTR_HEIGHT, parseValue(heightContent));
+        return result;
+    }
+
+    public static int parseValue(String attrValue) {
+        if (attrValue == null || "".equals(attrValue)) {
+            return 0;
+        }
+        try {
+            attrValue = attrValue.replaceAll(" ", "");
+            String value = attrValue.toLowerCase().replaceAll(UNIT, "");
+            return Integer.parseInt(value);
+        } catch (Exception e) {
+            return 0;
+        }
+    }
+
+    public static void mainTest(String[] args) {
+        String html = "<p><IMG src=\"abc\" width=\"800px\" height=\"400px\" style=\"width: 600PX; height:300PX;\"/><img src=\"xyz\" style=\"width:300px;\"/></p>";
+        log.debug(ImageUtils.reSizeImg(html));
+
+        Map<String, Integer> data = parseImageWidthAndHeight(html);
+        log.debug(data.get(ATTR_WIDTH) + " - " + data.get(ATTR_HEIGHT));
+    }
+
+}

+ 30 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/IoUtils.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+
+public class IoUtils {
+    private static final Logger log = LoggerFactory.getLogger(IoUtils.class);
+
+    public static boolean createFile(File file) {
+        try {
+            return file.createNewFile();
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+    public static boolean removeFile(File file) {
+        try {
+            return file.delete();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            return false;
+        }
+    }
+
+}

+ 25 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/Model.java

@@ -0,0 +1,25 @@
+/*
+ * *************************************************
+ * Copyright (c) 2019 QMTH. All Rights Reserved.
+ * Created by Deason on 2019-05-01 15:53:53.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base;
+
+import java.util.Optional;
+
+/**
+ * @author: fengdesheng
+ * @since: 2019/05/1
+ */
+public class Model {
+
+    public static <T> T of(Optional<T> optional) {
+        if (optional.isPresent()) {
+            return optional.get();
+        }
+        return null;
+    }
+
+}

+ 58 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/SpringContextUtils.java

@@ -0,0 +1,58 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+/**
+ * 获取ApplicationContext和Object的工具类
+ */
+@Component
+public class SpringContextUtils implements ApplicationContextAware {
+    private static ApplicationContext applicationContext;
+
+    public void setApplicationContext(ApplicationContext arg0) throws BeansException {
+        applicationContext = arg0;
+    }
+
+    /**
+     * 获取applicationContext对象
+     */
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    /**
+     * 获取Bean
+     */
+    public static Object getBeanById(String id) {
+        return applicationContext.getBean(id);
+    }
+
+    /**
+     * 获取Bean
+     */
+    public static <T> T getBean(Class<T> clazz, String beanName) {
+        return (T) applicationContext.getBean(beanName);
+    }
+
+    /**
+     * 根据bean的class来查找对象
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static Object getBeanByClass(Class c) {
+        return applicationContext.getBean(c);
+    }
+
+    /**
+     * 根据bean的class来查找所有的对象(包括子类)
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static Map getBeansByClass(Class c) {
+        return applicationContext.getBeansOfType(c);
+    }
+
+}

+ 276 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/StringSimilarityUtils.java

@@ -0,0 +1,276 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import org.ansj.domain.Result;
+import org.ansj.domain.Term;
+import org.ansj.splitWord.analysis.ToAnalysis;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.*;
+
+/**
+ * 计算相似度工具包:
+ *
+ * @author songyue
+ * @date 2016-05-11
+ */
+public class StringSimilarityUtils {
+
+    /**
+     * 对输入字符串分词
+     *
+     * @param str
+     * @return ArrayList
+     * @author songyue
+     */
+    public static List<String> segmentText(String str) {
+        List<String> segResult = new ArrayList<>();// 分词结果
+        Result result = ToAnalysis.parse(str);
+        List<Term> terms = result.getTerms();
+        for (Term term : terms) {
+            if (StringUtils.isNotEmpty(term.getName().trim())) {
+                segResult.add(term.getName());
+            }
+        }
+        return segResult;
+    }
+
+    /**
+     * 计算相似度(两个分词集合,分词匹配,算法为余弦定理)
+     *
+     * @param seg1
+     * @param seg2
+     * @return
+     */
+    public static double getSimilarityWithCosinesBySeg(String seg1, String seg2) {
+        double similarity = 0;
+        int size1 = 0;
+        int size2 = 0;
+        seg1 = stringFilter(seg1);
+        seg2 = stringFilter(seg2);
+        List<String> w1 = segmentText(seg1);
+        List<String> w2 = segmentText(seg2);
+        if (w1 != null && (size1 = w1.size()) != 0 && w2 != null && (size2 = w2.size()) != 0) {
+            Map<String, int[]> countMap = new HashMap<>();
+            String index = null;
+            // 将w1与w2分词出现频次统计入coutMap中
+            for (int i = 0; i < size1; i++) {
+                index = w1.get(i);
+                if (index != null) {
+                    int[] c = countMap.get(index);
+                    if (c != null && c.length == 2) {
+                        c[0]++;
+                    } else {
+                        c = new int[2];
+                        c[0] = 1;
+                        c[1] = 0;
+                        countMap.put(index, c);
+                    }
+                }
+            }
+            for (int i = 0; i < size2; i++) {
+                index = w2.get(i);
+                if (index != null) {
+                    int[] c = countMap.get(index);
+                    if (c != null && c.length == 2) {
+                        c[1]++;
+                    } else {
+                        c = new int[2];
+                        c[0] = 0;
+                        c[1] = 1;
+                        countMap.put(index, c);
+                    }
+                }
+            }
+            // 根据余弦定理计算相似度
+            Iterator<String> it = countMap.keySet().iterator();
+            double sum = 0;
+            double s1 = 0;
+            double s2 = 0;
+            while (it.hasNext()) {
+                int[] c = countMap.get(it.next());
+                sum += c[0] * c[1];
+                s1 += c[0] * c[0];
+                s2 += c[1] * c[1];
+            }
+            similarity = sum / Math.sqrt(s1 * s2);
+        } else {
+            return 0;
+        }
+        return similarity;
+    }
+
+    /**
+     * 计算相似度(两个字符串,全字匹配,算法为余弦定理)
+     *
+     * @param w1
+     * @param w2
+     * @return
+     */
+    public static double getSimilarityWithCosinesByWords(String w1, String w2) {
+        double similarity = 0;
+        int size1 = 0;
+        int size2 = 0;
+        w1 = stringFilter(w1);
+        w2 = stringFilter(w2);
+        if (w1 != null && (size1 = w1.length()) != 0 && w2 != null && (size2 = w2.length()) != 0) {
+            Map<Character, int[]> countMap = new HashMap<>();
+            char index;
+            // 将w1与w2所有字符出现频次统计入countMap中
+            for (int i = 0; i < size1; i++) {
+                index = w1.charAt(i);
+                int[] c = countMap.get(index);
+                if (c != null && c.length == 2) {
+                    c[0]++;
+                } else {
+                    c = new int[2];
+                    c[0] = 1;
+                    c[1] = 0;
+                    countMap.put(index, c);
+                }
+            }
+            for (int i = 0; i < size2; i++) {
+                index = w2.charAt(i);
+                int[] c = countMap.get(index);
+                if (c != null && c.length == 2) {
+                    c[1]++;
+                } else {
+                    c = new int[2];
+                    c[0] = 0;
+                    c[1] = 1;
+                    countMap.put(index, c);
+                }
+            }
+            // 根据余弦定理计算相似度
+            Iterator<Character> it = countMap.keySet().iterator();
+            double sum = 0;
+            double s1 = 0;
+            double s2 = 0;
+            while (it.hasNext()) {
+                int[] c = countMap.get(it.next());
+                sum += c[0] * c[1];
+                s1 += c[0] * c[0];
+                s2 += c[1] * c[1];
+            }
+            similarity = sum / Math.sqrt(s1 * s2);
+        } else {
+            throw new NullPointerException("传入的参数为空");
+        }
+        return similarity;
+    }
+
+    /**
+     * 计算相似度(两个字符串,采用优化Dice算法)
+     *
+     * @param w1
+     * @param w2
+     * @return
+     */
+    public static double getSimilarityWithDiceOptByWords(String w1, String w2) {
+        if (w1 == null || w2 == null || w1.length() == 0 || w2.length() == 0)
+            return 0;
+
+        if (w1.equals(w2))
+            return 1;
+
+        if (w1.length() == 1 || w2.length() == 1) {
+            if (w1.equals(w2)) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+
+        w1 = stringFilter(w1);
+        w2 = stringFilter(w2);
+
+        final int n = w1.length() - 1;
+        final int[] sPairs = new int[n];
+        for (int i = 0; i <= n; i++)
+            if (i == 0)
+                sPairs[i] = w1.charAt(i) << 16;
+            else if (i == n)
+                sPairs[i - 1] |= w1.charAt(i);
+            else
+                sPairs[i] = (sPairs[i - 1] |= w1.charAt(i)) << 16;
+
+        final int m = w2.length() - 1;
+        final int[] tPairs = new int[m];
+        for (int i = 0; i <= m; i++)
+            if (i == 0)
+                tPairs[i] = w2.charAt(i) << 16;
+            else if (i == m)
+                tPairs[i - 1] |= w2.charAt(i);
+            else
+                tPairs[i] = (tPairs[i - 1] |= w2.charAt(i)) << 16;
+
+        Arrays.sort(sPairs);
+        Arrays.sort(tPairs);
+
+        int matches = 0, i = 0, j = 0;
+        while (i < n && j < m) {
+            if (sPairs[i] == tPairs[j]) {
+                matches += 2;
+                i++;
+                j++;
+            } else if (sPairs[i] < tPairs[j])
+                i++;
+            else
+                j++;
+        }
+        return (double) matches / (n + m);
+    }
+
+    /**
+     * 计算相似度(两个字符串,采用一般Dice算法)
+     *
+     * @param w1
+     * @param w2
+     * @return
+     */
+    public static double getSimilarityWithDiceByWords(String w1, String w2) {
+        double similarity = 0;
+        if (w1 != null && w1.length() != 0 && w2 != null && w2.length() != 0) {
+            if (w1.length() == 1 || w2.length() == 1) {
+                if (w1.equals(w2)) {
+                    return 1;
+                } else {
+                    return 0;
+                }
+            }
+            w1 = stringFilter(w1);
+            w2 = stringFilter(w2);
+            Set<String> nx = new HashSet<>();
+            Set<String> ny = new HashSet<>();
+
+            for (int i = 0; i < w1.length() - 1; i++) {
+                char x1 = w1.charAt(i);
+                char x2 = w1.charAt(i + 1);
+                String tmp = "" + x1 + x2;
+                nx.add(tmp);
+            }
+            for (int j = 0; j < w2.length() - 1; j++) {
+                char y1 = w2.charAt(j);
+                char y2 = w2.charAt(j + 1);
+                String tmp = "" + y1 + y2;
+                ny.add(tmp);
+            }
+            Set<String> intersection = new HashSet<>(nx);
+            intersection.retainAll(ny);
+            double totcombigrams = intersection.size();
+            similarity = (2 * totcombigrams) / (nx.size() + ny.size());
+        }
+        return similarity;
+    }
+
+    /**
+     * 过滤特殊字符
+     *
+     * @param str
+     * @return
+     */
+    public static String stringFilter(String str) {
+        String regEx = "\\&[a-zA-Z]{1,10};|[_`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
+        return str.replaceAll(regEx, "").trim();
+    }
+
+}

+ 74 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/ZipUtils.java

@@ -0,0 +1,74 @@
+package cn.com.qmth.examcloud.core.questions.base;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class ZipUtils {
+    private static final Logger log = LoggerFactory.getLogger(ZipUtils.class);
+
+    public static void doCompress(String srcFile, String zipFile) {
+        doCompress(new File(srcFile), new File(zipFile));
+    }
+
+    /**
+     * 文件压缩
+     *
+     * @param srcFile  目录或者单个文件
+     * @param destFile 压缩后的ZIP文件
+     */
+    public static void doCompress(File srcFile, File destFile) {
+        try (FileOutputStream fos = new FileOutputStream(destFile);
+             ZipOutputStream out = new ZipOutputStream(fos);) {
+
+            if (srcFile.isDirectory()) {
+                File[] files = srcFile.listFiles();
+                for (File file : files) {
+                    doCompress(file, out);
+                }
+            } else {
+                doCompress(srcFile, out);
+            }
+        } catch (FileNotFoundException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    public static void doCompress(String pathname, ZipOutputStream out) {
+        doCompress(new File(pathname), out);
+    }
+
+    public static void doCompress(File file, ZipOutputStream out) {
+        if (!file.exists()) {
+            return;
+        }
+
+        try (FileInputStream fis = new FileInputStream(file);) {
+            ZipEntry entry = new ZipEntry(file.getName());
+            out.putNextEntry(entry);
+
+            byte[] buffer = new byte[1024];
+            int len;
+            // 读取文件的内容,打包到zip文件
+            while ((len = fis.read(buffer)) > 0) {
+                out.write(buffer, 0, len);
+            }
+
+            out.flush();
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            try {
+                out.closeEntry();
+            } catch (IOException e) {
+                //ignore
+            }
+        }
+    }
+
+}

+ 35 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/bean/ExportServiceManageBean.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.questions.base.bean;
+
+public class ExportServiceManageBean {
+
+    private String id;
+
+    private String orgId;
+
+    private String exportServiceName;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getExportServiceName() {
+        return exportServiceName;
+    }
+
+    public void setExportServiceName(String exportServiceName) {
+        this.exportServiceName = exportServiceName;
+    }
+
+    public String getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(String orgId) {
+        this.orgId = orgId;
+    }
+
+}

+ 38 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/Constants.java

@@ -0,0 +1,38 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model;
+
+public interface Constants {
+
+    String ZIP = ".zip";
+
+    String JSON = ".json";
+
+    String TEXT = "text";
+
+    String WRONG = "错误";
+
+    String WRONG_NUM = "1";
+
+    String CORRECT = "正确";
+
+    String CORRECT_NUM = "2";
+
+    String IMAGE = "image";
+
+    String AUDIO = "audio";
+
+    String TAG_IMG = " <img style=\"%s\" src=\"%s\" /> ";
+
+    String FILE_PAPER = "paper.json";//试卷结构文件
+
+    String FILE_POLICY = "policy.json";//课程单独组卷配置文件
+
+    String FILE_ANSWER = "answer.json";//答案内容文件
+
+}

+ 43 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/PolicyType.java

@@ -0,0 +1,43 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:09.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model;
+
+/**
+ * 调卷规则类型
+ */
+public enum PolicyType {
+
+    Random("随机抽卷", 1), BlueMap("蓝图组卷", 2);
+
+    private String name;
+
+    private int id;
+
+    PolicyType(String name, int id) {
+        this.name = name;
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public static PolicyType findById(int id) {
+        for (PolicyType type : PolicyType.values()) {
+            if (type.id == id) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+}

+ 126 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/QuesStructType.java

@@ -0,0 +1,126 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:09.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model;
+
+public enum QuesStructType {
+    SINGLE_ANSWER_QUESTION(1, "单选题", true, false),
+    MULTIPLE_ANSWER_QUESTION(2, "多选题", true, false),
+    BOOL_ANSWER_QUESTION(3, "判断题", true, false),
+    FILL_BLANK_QUESTION(4, "填空题", false, false),
+    TEXT_ANSWER_QUESTION(5, "问答题", false, false),
+    NESTED_ANSWER_QUESTION(6, "套题", false, true);
+
+    private int id;
+    private String name;
+    private boolean objective;//是否是客观题
+    private boolean combine;//是否是组合题
+
+    QuesStructType(int id, String name, boolean objective, boolean combine) {
+        this.id = id;
+        this.name = name;
+        this.objective = objective;
+        this.combine = combine;
+    }
+
+    /**
+     * 通过ID获取试题类型
+     */
+    public static QuesStructType getTypeById(Integer id) {
+        if (id == null) {
+            return null;
+        }
+        for (QuesStructType type : QuesStructType.values()) {
+            if (id.equals(type.getId())) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 通过名称获取试题类型
+     */
+    public static QuesStructType getTypeByName(String name) {
+        if (name == null) {
+            return null;
+        }
+        for (QuesStructType type : QuesStructType.values()) {
+            if (type.getName().equals(name)) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 根据ID判断是否为客观题
+     */
+    public static boolean isObjective(Integer id) {
+        if (id == null) {
+            return false;
+        }
+        for (QuesStructType type : QuesStructType.values()) {
+            if (id.equals(type.getId())) {
+                return type.objective;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 根据名称判断是否为客观题
+     */
+    public static boolean isObjective(String name) {
+        if (name == null) {
+            return false;
+        }
+        for (QuesStructType type : QuesStructType.values()) {
+            if (type.getName().equals(name)) {
+                return type.objective;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 根据ID判断是否为套题
+     */
+    public static boolean isCombine(Integer id) {
+        if (id == null) {
+            return false;
+        }
+
+        for (QuesStructType type : QuesStructType.values()) {
+            if (id.equals(type.getId())) {
+                return type.combine;
+            }
+        }
+        return false;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isObjective() {
+        return objective;
+    }
+
+    public boolean isCombine() {
+        return combine;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

+ 88 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/Result.java

@@ -0,0 +1,88 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import java.io.Serializable;
+
+public class Result<T> implements Serializable {
+    private static final long serialVersionUID = 1L;
+    private boolean success;//是否成功
+    private String message;//描述信息
+    private T data;//结果数据
+
+    public Result() {
+
+    }
+
+    public Result(boolean success, String message, T data) {
+        this.success = success;
+        this.message = message;
+        this.data = data;
+    }
+
+    /**
+     * 成功结果
+     */
+    public Result success(T data) {
+        this.success = true;
+        this.data = data;
+        return this;
+    }
+
+    public Result success() {
+        this.success = true;
+        return this;
+    }
+
+    /**
+     * 错误结果
+     */
+    public Result error(String message) {
+        this.success = false;
+        this.message = message;
+        return this;
+    }
+
+    public Result error() {
+        this.success = false;
+        return this;
+    }
+
+    public boolean isSuccess() {
+        return success;
+    }
+
+    public void setSuccess(boolean success) {
+        this.success = success;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+    @Override
+    public String toString() {
+        return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
+    }
+
+}

+ 54 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PaperAnswerDto.java

@@ -0,0 +1,54 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 试卷答案
+ */
+public class PaperAnswerDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String id;//注:这里的Id对应试卷代码
+    private List<SimpleQuestionDto> questions;//大题列表
+
+    public PaperAnswerDto(String id) {
+        this.id = id;
+    }
+
+    public void addQuestion(SimpleQuestionDto question) {
+        if (questions == null) {
+            questions = new ArrayList<>();
+        }
+        questions.add(question);
+    }
+
+
+    public PaperAnswerDto() {
+
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public List<SimpleQuestionDto> getQuestions() {
+        return questions;
+    }
+
+    public void setQuestions(List<SimpleQuestionDto> questions) {
+        this.questions = questions;
+    }
+
+}

+ 93 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PaperDetailDto.java

@@ -0,0 +1,93 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.PaperDetailVo;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 试卷的大题项
+ */
+public class PaperDetailDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private Integer number;//序号
+    private String name;//题型名称
+    private Double totalScore;//总分
+    private Integer questionCount;//题数量
+    private List<QuestionDto> questions;//试题列表
+
+    public PaperDetailDto(PaperDetailVo detail) {
+        this.number = detail.getNumber();
+        this.name = detail.getName();
+        this.totalScore = detail.getTotalScore();
+        this.questionCount = detail.getQuestionCount();
+    }
+
+    public PaperDetailDto(String name) {
+        this.name = name;
+    }
+
+    public PaperDetailDto() {
+
+    }
+
+    public void addQuestion(QuestionDto question) {
+        if (questions == null) {
+            questions = new ArrayList<>();
+        }
+        questions.add(question);
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getQuestionCount() {
+        //return questionCount;
+        return getQuestions().size();
+    }
+
+    public void setQuestionCount(Integer questionCount) {
+        this.questionCount = questionCount;
+    }
+
+    public List<QuestionDto> getQuestions() {
+        if (questions == null) {
+            return new ArrayList<>();
+        }
+        return questions;
+    }
+
+    public void setQuestions(List<QuestionDto> questions) {
+        this.questions = questions;
+    }
+
+}

+ 97 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PaperDto.java

@@ -0,0 +1,97 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.PaperVo;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 试卷
+ */
+public class PaperDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String id;//注:这里的Id对应试卷代码
+    private String name;//试卷名称
+    private String courseCode;//课程代码
+    private String paperType;//试卷类型
+    private Double totalScore;//试卷总分
+    private Integer detailCount;//大题数量
+    private List<PaperDetailDto> details;//大题列表
+
+    public PaperDto(PaperVo paper) {
+        this.id = paper.getId();
+        this.name = paper.getName();
+        this.courseCode = paper.getCourseCode();
+        //this.paperType = "A";
+        this.totalScore = paper.getTotalScore();
+        this.detailCount = paper.getDetailCount();
+    }
+
+    public PaperDto() {
+
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getDetailCount() {
+        return detailCount;
+    }
+
+    public void setDetailCount(Integer detailCount) {
+        this.detailCount = detailCount;
+    }
+
+    public List<PaperDetailDto> getDetails() {
+        return details;
+    }
+
+    public void setDetails(List<PaperDetailDto> details) {
+        this.details = details;
+    }
+
+}

+ 81 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PolicyDto.java

@@ -0,0 +1,81 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 抽卷规则配置
+ */
+public class PolicyDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String courseCode;//课程代码
+    private String paperType;//试卷类型
+    private Integer type;//规则类型
+    private Boolean objectiveShuffle;//是否主观题乱序
+    private Boolean optionShuffle;//是否选项乱序
+    private List<PolicyWeightDto> weights;//试卷权重列表
+
+    public void addWeight(PolicyWeightDto weight) {
+        if (weights == null) {
+            weights = new ArrayList<>();
+        }
+        weights.add(weight);
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getPaperType() {
+        return paperType;
+    }
+
+    public void setPaperType(String paperType) {
+        this.paperType = paperType;
+    }
+
+    public Integer getType() {
+        return type;
+    }
+
+    public void setType(Integer type) {
+        this.type = type;
+    }
+
+    public Boolean getObjectiveShuffle() {
+        return objectiveShuffle;
+    }
+
+    public void setObjectiveShuffle(Boolean objectiveShuffle) {
+        this.objectiveShuffle = objectiveShuffle;
+    }
+
+    public Boolean getOptionShuffle() {
+        return optionShuffle;
+    }
+
+    public void setOptionShuffle(Boolean optionShuffle) {
+        this.optionShuffle = optionShuffle;
+    }
+
+    public List<PolicyWeightDto> getWeights() {
+        return weights;
+    }
+
+    public void setWeights(List<PolicyWeightDto> weights) {
+        this.weights = weights;
+    }
+
+}

+ 45 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/PolicyWeightDto.java

@@ -0,0 +1,45 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:09.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import java.io.Serializable;
+
+/**
+ * 试卷权重规则
+ */
+public class PolicyWeightDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String paperId;//注:这里的Id对应试卷代码
+    private Double weight;//权重值
+
+    public PolicyWeightDto(String paperId, Double weight) {
+        this.paperId = paperId;
+        this.weight = weight;
+    }
+
+    public PolicyWeightDto() {
+
+    }
+
+    public String getPaperId() {
+        return paperId;
+    }
+
+    public void setPaperId(String paperId) {
+        this.paperId = paperId;
+    }
+
+    public Double getWeight() {
+        return weight;
+    }
+
+    public void setWeight(Double weight) {
+        this.weight = weight;
+    }
+
+}

+ 189 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/QuestionDto.java

@@ -0,0 +1,189 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import cn.com.qmth.examcloud.core.questions.base.converter.model.Constants;
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.*;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 试题
+ */
+public class QuestionDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String id;
+    private Integer number;//题序号
+    private Double score;//满分
+    private Integer structType;//题类型
+    private Boolean objective;//是否主观题
+    private String body;//题干
+    private String answer;//正确答案
+    private List<QuestionOptionDto> options;//题选项列表
+    private List<QuestionDto> subQuestions;//套题下的子题列表
+
+    public QuestionDto(QuestionVo question) {
+        this.id = question.getId();
+        this.number = question.getNumber();
+        this.score = question.getScore() != null ? question.getScore() : 1d;
+        this.structType = question.getStructType();
+        this.objective = question.getObjective();
+
+        //转换题干内容
+        StringBuilder bodyStr = new StringBuilder();
+        BodyVo body = question.getBody();
+        if (body != null && body.hasSection()) {
+            for (SectionVo section : body.getSections()) {
+                if (section.hasBlock()) {
+                    for (BlockVo block : section.getBlocks()) {
+                        if (Constants.TEXT.equals(block.getType())) {
+                            bodyStr.append(block.getValue());
+                        } else if (Constants.IMAGE.equals(block.getType())) {
+                            ParamVo param = block.getParam();
+                            if (param != null) {
+                                bodyStr.append(String.format(Constants.TAG_IMG, param.getStyle(), block.getValue()));
+                            } else {
+                                bodyStr.append(String.format(Constants.TAG_IMG, "", block.getValue()));
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        this.body = bodyStr.toString();
+
+        //转换选项内容
+        if (question.hasOption()) {
+            for (OptionVo option : question.getOptions()) {
+                StringBuilder optionBodyStr = new StringBuilder();
+                BodyVo optionBody = option.getBody();
+                if (optionBody != null && optionBody.hasSection()) {
+                    for (SectionVo section : optionBody.getSections()) {
+                        if (section.hasBlock()) {
+                            for (BlockVo block : section.getBlocks()) {
+                                if (Constants.TEXT.equals(block.getType())) {
+                                    optionBodyStr.append(block.getValue());
+                                } else if (Constants.IMAGE.equals(block.getType())) {
+                                    ParamVo param = block.getParam();
+                                    if (param != null) {
+                                        optionBodyStr.append(String.format(Constants.TAG_IMG, param.getStyle(), block.getValue()));
+                                    } else {
+                                        optionBodyStr.append(String.format(Constants.TAG_IMG, "", block.getValue()));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                QuestionOptionDto newOption = new QuestionOptionDto();
+                newOption.setNumber(option.getNumber());
+                newOption.setBody(optionBodyStr.toString());
+                this.addOption(newOption);
+            }
+        }
+    }
+
+    public void addOption(QuestionOptionDto option) {
+        if (options == null) {
+            options = new ArrayList<>();
+        }
+        options.add(option);
+    }
+
+    public void addSubQuestion(QuestionDto question) {
+        if (subQuestions == null) {
+            subQuestions = new ArrayList<>();
+        }
+        subQuestions.add(question);
+    }
+
+    public QuestionDto() {
+
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    public Integer getStructType() {
+        return structType;
+    }
+
+    public void setStructType(Integer structType) {
+        this.structType = structType;
+    }
+
+    public Boolean getObjective() {
+        return objective;
+    }
+
+    public void setObjective(Boolean objective) {
+        this.objective = objective;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
+    public List<QuestionOptionDto> getOptions() {
+        if (options == null) {
+            return new ArrayList<>();
+        }
+        return options;
+    }
+
+    public void setOptions(List<QuestionOptionDto> options) {
+        this.options = options;
+    }
+
+    public List<QuestionDto> getSubQuestions() {
+        if (subQuestions == null) {
+            return new ArrayList<>();
+        }
+        return subQuestions;
+    }
+
+    public void setSubQuestions(List<QuestionDto> subQuestions) {
+        this.subQuestions = subQuestions;
+    }
+
+}

+ 36 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/QuestionOptionDto.java

@@ -0,0 +1,36 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import java.io.Serializable;
+
+/**
+ * 试题的答案项
+ */
+public class QuestionOptionDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private Integer number;//答案序号
+    private String body;//答案内容
+
+    public Integer getNumber() {
+        return number != null ? number : 1;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public void setBody(String body) {
+        this.body = body;
+    }
+
+}

+ 112 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/dps/SimpleQuestionDto.java

@@ -0,0 +1,112 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.dps;
+
+import cn.com.qmth.examcloud.core.questions.base.converter.model.Constants;
+import cn.com.qmth.examcloud.core.questions.base.converter.model.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.AnswerVo;
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.BlockVo;
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.QuestionVo;
+import cn.com.qmth.examcloud.core.questions.base.converter.model.platform.SectionVo;
+import cn.com.qmth.examcloud.core.questions.base.converter.utils.NumUtil;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 试题
+ */
+public class SimpleQuestionDto implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String id;
+    private Integer number;//题号
+    private String answer;//正确答案
+    private List<SimpleQuestionDto> subQuestions;//套题下的子题列表
+
+    public SimpleQuestionDto(QuestionVo question) {
+        this.id = question.getId();
+        QuesStructType type = QuesStructType.getTypeById(question.getStructType());
+        if (type == null) {
+            throw new IllegalArgumentException("Question struct type is wrong");
+        }
+        //转换答案内容
+        StringBuilder answerStr = new StringBuilder();
+        AnswerVo answer = question.getAnswer();
+        if (answer != null && answer.hasSection()) {
+            for (SectionVo section : answer.getSections()) {
+                if (section.hasBlock()) {
+                    for (BlockVo block : section.getBlocks()) {
+                        if (Constants.TEXT.equals(block.getType())) {
+                            String value = block.getValue();
+                            if (type.isObjective()) {
+                                if (QuesStructType.SINGLE_ANSWER_QUESTION.getId() == type.getId()) {
+                                    value = NumUtil.toNumber(value);
+                                } else if (QuesStructType.MULTIPLE_ANSWER_QUESTION.getId() == type.getId()) {
+                                    value = NumUtil.toNumber(value);
+                                } else if (QuesStructType.BOOL_ANSWER_QUESTION.getId() == type.getId()) {
+                                    if (Constants.CORRECT.equals(value)) {
+                                        value = Constants.CORRECT_NUM;
+                                    } else {
+                                        value = Constants.WRONG_NUM;
+                                    }
+                                }
+                            }
+                            answerStr.append(value);
+                        }
+                    }
+                }
+            }
+        }
+        this.answer = answerStr.toString();
+    }
+
+    public void addSubQuestion(SimpleQuestionDto question) {
+        if (subQuestions == null) {
+            subQuestions = new ArrayList<>();
+        }
+        subQuestions.add(question);
+    }
+
+    public SimpleQuestionDto() {
+
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public String getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(String answer) {
+        this.answer = answer;
+    }
+
+    public List<SimpleQuestionDto> getSubQuestions() {
+        return subQuestions;
+    }
+
+    public void setSubQuestions(List<SimpleQuestionDto> subQuestions) {
+        this.subQuestions = subQuestions;
+    }
+
+}

+ 35 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/AnswerVo.java

@@ -0,0 +1,35 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 答案信息
+ */
+public class AnswerVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private List<SectionVo> sections;//分类列表
+
+    public boolean hasSection() {
+        if (sections != null && sections.size() > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    public List<SectionVo> getSections() {
+        return sections;
+    }
+
+    public void setSections(List<SectionVo> sections) {
+        this.sections = sections;
+    }
+
+}

+ 54 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/BlockVo.java

@@ -0,0 +1,54 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+
+/**
+ * 模块信息
+ */
+public class BlockVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String type;//模块类型
+    private String value;//模块内容
+    private Integer playTime;//播放次数
+    private ParamVo param;//附属参数
+
+    public String getType() {
+        return type != null ? type : "";
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getValue() {
+        return value != null ? value : "";
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public Integer getPlayTime() {
+        return playTime;
+    }
+
+    public void setPlayTime(Integer playTime) {
+        this.playTime = playTime;
+    }
+
+    public ParamVo getParam() {
+        return param;
+    }
+
+    public void setParam(ParamVo param) {
+        this.param = param;
+    }
+
+}

+ 35 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/BodyVo.java

@@ -0,0 +1,35 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 题干信息
+ */
+public class BodyVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private List<SectionVo> sections;//分类列表
+
+    public boolean hasSection() {
+        if (sections != null && sections.size() > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    public List<SectionVo> getSections() {
+        return sections;
+    }
+
+    public void setSections(List<SectionVo> sections) {
+        this.sections = sections;
+    }
+
+}

+ 44 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/OptionVo.java

@@ -0,0 +1,44 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+
+/**
+ * 选项信息
+ */
+public class OptionVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private Integer number;//题序号
+    private Boolean correct;//是否正确答案
+    private BodyVo body;//内容
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Boolean getCorrect() {
+        return correct;
+    }
+
+    public void setCorrect(Boolean correct) {
+        this.correct = correct;
+    }
+
+    public BodyVo getBody() {
+        return body;
+    }
+
+    public void setBody(BodyVo body) {
+        this.body = body;
+    }
+}

+ 68 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/PaperDetailVo.java

@@ -0,0 +1,68 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 试卷的大题项
+ */
+public class PaperDetailVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private Integer number;//序号
+    private String name;//题型名称
+    private Double totalScore;//总分
+    private Integer questionCount;//题数量
+    private List<QuestionVo> questions;//试题列表
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getQuestionCount() {
+        return questionCount;
+    }
+
+    public void setQuestionCount(Integer questionCount) {
+        this.questionCount = questionCount;
+    }
+
+    public List<QuestionVo> getQuestions() {
+        if (questions == null) {
+            return new ArrayList<>();
+        }
+        return questions;
+    }
+
+    public void setQuestions(List<QuestionVo> questions) {
+        this.questions = questions;
+    }
+
+}

+ 91 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/PaperVo.java

@@ -0,0 +1,91 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 试卷
+ */
+public class PaperVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String id;//注:这里的Id对应试卷代码
+    private String name;//试卷名称
+    private String courseCode;//课程代码
+    private String courseName;//课程名称
+    private Double totalScore;//试卷总分
+    private Integer hasVideo;//是否有音频
+    private Integer detailCount;//大题数量
+    private List<PaperDetailVo> details;//大题列表
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    public Integer getHasVideo() {
+        return hasVideo;
+    }
+
+    public void setHasVideo(Integer hasVideo) {
+        this.hasVideo = hasVideo;
+    }
+
+    public Integer getDetailCount() {
+        return detailCount;
+    }
+
+    public void setDetailCount(Integer detailCount) {
+        this.detailCount = detailCount;
+    }
+
+    public List<PaperDetailVo> getDetails() {
+        return details;
+    }
+
+    public void setDetails(List<PaperDetailVo> details) {
+        this.details = details;
+    }
+
+}

+ 48 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/ParamVo.java

@@ -0,0 +1,48 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+
+/**
+ * 模块信息
+ */
+public class ParamVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String width;
+    private String height;
+
+    public String getStyle() {
+        return "max-width:500px;max-height:500px;" + getWidth() + getHeight();
+    }
+
+    public String getWidth() {
+        if (StringUtils.isBlank(width)) {
+            return "";
+        }
+        return String.format("width:%spx;", width);
+    }
+
+    public void setWidth(String width) {
+        this.width = width;
+    }
+
+    public String getHeight() {
+        if (StringUtils.isBlank(height)) {
+            return "";
+        }
+        return String.format("height:%spx;", height);
+    }
+
+    public void setHeight(String height) {
+        this.height = height;
+    }
+
+}

+ 113 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/QuestionVo.java

@@ -0,0 +1,113 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 试题
+ */
+public class QuestionVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private String id;
+    private Integer number;//题序号
+    private Double score;//满分
+    private Integer structType;//题类型
+    private Boolean objective;//是否主观题
+    private BodyVo body;//题干
+    private AnswerVo answer;//答案
+    private List<OptionVo> options;//选项
+    private List<QuestionVo> subQuestions;//套题下的子题列表
+
+    public boolean hasOption() {
+        if (options != null && options.size() > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    public boolean hasSubQuestion() {
+        if (subQuestions != null && subQuestions.size() > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public Integer getNumber() {
+        return number;
+    }
+
+    public void setNumber(Integer number) {
+        this.number = number;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    public Integer getStructType() {
+        return structType;
+    }
+
+    public void setStructType(Integer structType) {
+        this.structType = structType;
+    }
+
+    public Boolean getObjective() {
+        return objective;
+    }
+
+    public void setObjective(Boolean objective) {
+        this.objective = objective;
+    }
+
+    public BodyVo getBody() {
+        return body;
+    }
+
+    public void setBody(BodyVo body) {
+        this.body = body;
+    }
+
+    public AnswerVo getAnswer() {
+        return answer;
+    }
+
+    public void setAnswer(AnswerVo answer) {
+        this.answer = answer;
+    }
+
+    public List<OptionVo> getOptions() {
+        return options;
+    }
+
+    public void setOptions(List<OptionVo> options) {
+        this.options = options;
+    }
+
+    public List<QuestionVo> getSubQuestions() {
+        return subQuestions;
+    }
+
+    public void setSubQuestions(List<QuestionVo> subQuestions) {
+        this.subQuestions = subQuestions;
+    }
+}

+ 35 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/model/platform/SectionVo.java

@@ -0,0 +1,35 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.model.platform;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分类信息
+ */
+public class SectionVo implements Serializable {
+    private static final long serialVersionUID = 2180336388838138300L;
+    private List<BlockVo> blocks;//模块列表
+
+    public boolean hasBlock() {
+        if (blocks != null && blocks.size() > 0) {
+            return true;
+        }
+        return false;
+    }
+
+    public List<BlockVo> getBlocks() {
+        return blocks;
+    }
+
+    public void setBlocks(List<BlockVo> blocks) {
+        this.blocks = blocks;
+    }
+
+}

+ 225 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/Cryptogram.java

@@ -0,0 +1,225 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.utils;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+
+/**
+ * 支持DES/AES对称加密的工具类
+ */
+public class Cryptogram {
+    private static Logger log = LoggerFactory.getLogger(Cryptogram.class);
+    private static final String AES = "AES";
+    private static final String AES_CBC = "AES/CBC/PKCS5Padding";
+    private static final String IV_SPEC = "0102030405060708";
+    private static final int DEFAULT_AES_KEY_SIZE = 128;
+    private static final int DEFAULT_IV_SIZE = 16;
+    private static SecureRandom random = new SecureRandom();
+
+    /**
+     * 使用AES加密字符串
+     *
+     * @param str 待加密字符串
+     * @param key 符合AES要求的密钥
+     * @return
+     */
+    public static String aesEncrypt(String str, String key) {
+        if (str == null || key == null) {
+            return null;
+        }
+        try {
+            byte[] strBytes = str.getBytes("UTF-8");
+            byte[] keyBytes = key.getBytes("ASCII");
+//            byte[] ivBytes = IV_SPEC.getBytes();//默认向量值
+//            byte[] bytes = aes(strBytes, keyBytes, ivBytes, Cipher.ENCRYPT_MODE);
+            byte[] bytes = aes(strBytes, keyBytes, Cipher.ENCRYPT_MODE);
+            return byteToHex(bytes);
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 使用AES解密字符串
+     *
+     * @param str 待解密字符串
+     * @param key 符合AES要求的密钥
+     * @return
+     */
+    public static String aesDecrypt(String str, String key) {
+        if (str == null || key == null) {
+            return null;
+        }
+        try {
+            byte[] strBytes = hexToByte(str);
+            byte[] keyBytes = key.getBytes("ASCII");
+//            byte[] ivBytes = IV_SPEC.getBytes();//默认向量值
+//            byte[] decryptResult = aes(strBytes, keyBytes, ivBytes, Cipher.DECRYPT_MODE);
+            byte[] decryptResult = aes(strBytes, keyBytes, Cipher.DECRYPT_MODE);
+            return new String(decryptResult);
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果
+     *
+     * @param input    原始字节数组
+     * @param keyBytes 符合AES要求的密钥
+     * @param mode     Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
+     */
+    public static byte[] aes(byte[] input, byte[] keyBytes, int mode) {
+        try {
+            SecretKey secretKey = new SecretKeySpec(keyBytes, AES);
+            Cipher cipher = Cipher.getInstance(AES);
+            cipher.init(mode, secretKey);
+            return cipher.doFinal(input);
+        } catch (BadPaddingException e) {
+            log.error("加解密密匙无效:" + e.getMessage());
+            return null;
+        } catch (Exception e) {
+            log.error("加解密错误:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 使用AES加密或解密无编码的原始字节数组, 返回无编码的字节数组结果
+     *
+     * @param input    原始字节数组
+     * @param keyBytes 符合AES要求的密钥
+     * @param ivBytes  初始向量
+     * @param mode     Cipher.ENCRYPT_MODE 或 Cipher.DECRYPT_MODE
+     */
+    public static byte[] aes(byte[] input, byte[] keyBytes, byte[] ivBytes, int mode) {
+        try {
+            SecretKey secretKey = new SecretKeySpec(keyBytes, AES);
+            Cipher cipher = Cipher.getInstance(AES_CBC);
+            IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
+            cipher.init(mode, secretKey, ivSpec);
+            return cipher.doFinal(input);
+        } catch (BadPaddingException e) {
+            log.error("加解密密匙无效:" + e.getMessage());
+            return null;
+        } catch (Exception e) {
+            log.error("加解密错误:" + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 生成AES密钥,返回字节数组, 默认长度为128位(16字节)
+     */
+    public static byte[] generateAesKey() {
+        try {
+            KeyGenerator keyGenerator = KeyGenerator.getInstance(AES);
+            keyGenerator.init(DEFAULT_AES_KEY_SIZE);
+            SecretKey secretKey = keyGenerator.generateKey();
+            return secretKey.getEncoded();
+        } catch (GeneralSecurityException e) {
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 生成随机向量,默认大小为cipher.getBlockSize(), 16字节
+     */
+    public static byte[] generateIV() {
+        byte[] bytes = new byte[DEFAULT_IV_SIZE];
+        random.nextBytes(bytes);
+        return bytes;
+    }
+
+    public static String byteToHex(byte[] bytes) {
+        StringBuilder hex = new StringBuilder();
+        for (int n = 0; n < bytes.length; n++) {
+            String tmp = (Integer.toHexString(bytes[n] & 0XFF));
+            if (tmp.length() == 1) {
+                hex.append("0").append(tmp);
+            } else {
+                hex.append(tmp);
+            }
+        }
+        return hex.toString().toUpperCase();
+    }
+
+    public static byte[] hexToByte(String strHex) {
+        if (strHex == null) {
+            return null;
+        }
+        int size = strHex.length();
+        if (size % 2 == 1) {
+            return null;
+        }
+        byte[] bytes = new byte[size / 2];
+        for (int i = 0; i != size / 2; i++) {
+            String s = strHex.substring(i * 2, i * 2 + 2);
+            bytes[i] = (byte) Integer.parseInt(s, 16);
+        }
+        return bytes;
+    }
+
+    /**
+     * 将字符串MD5后,再从MD5后的字符串里截取前后8位,合成16位KEY(转大写)
+     */
+    public static String convertKey(String password) {
+        if (password == null) {
+            return null;
+        }
+        try {
+            String md5 = md5Encrypt(password);
+            //MD5后字符串长度默认32位
+            String start = md5.substring(0, 8);
+            String end = md5.substring(md5.length() - 8, md5.length());
+            return (start + end).toUpperCase();
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 将密码MD5两次后返回
+     */
+    public static String md5Key(String password) {
+        if (password == null) {
+            return null;
+        }
+        try {
+            String md5 = md5Encrypt(password);
+            return md5Encrypt(md5);
+        } catch (Exception e) {
+            log.error(e.getMessage());
+            return null;
+        }
+    }
+
+    public static String md5Encrypt(String str) {
+        if (StringUtils.isEmpty(str)) {
+            return str;
+        }
+        return DigestUtils.md5Hex(str);
+    }
+
+}

+ 480 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/FileUtil.java

@@ -0,0 +1,480 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.utils;
+
+import cn.com.qmth.examcloud.core.questions.base.IoUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+public class FileUtil {
+    private static Logger log = LoggerFactory.getLogger(FileUtil.class);
+
+    /**
+     * 分隔文件
+     *
+     * @param sourcePath 原文件
+     * @param targetPath 目标文件
+     * @param n          跳过的字节数
+     * @return
+     */
+    public static File cutFile(String sourcePath, String targetPath, int n) {
+        File file = new File(sourcePath);
+        File newFile = new File(targetPath);
+
+        try (
+                FileInputStream fis = new FileInputStream(file);
+                InputStream is = new BufferedInputStream(fis);
+                OutputStream os = new FileOutputStream(newFile);
+        ) {
+
+            //从n个字节开始读,注意中文是两个字节
+            fis.skip(n);
+
+            //指定文件位置读取的文件流,存入新文件
+            byte buffer[] = new byte[4 * 1024];
+            int len;
+            while ((len = is.read(buffer)) != -1) {
+                os.write(buffer, 0, len);
+            }
+
+            os.flush();
+            return newFile;
+        } catch (FileNotFoundException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+        return null;
+    }
+
+    /**
+     * 读取文件前面部分N个字节
+     *
+     * @param path       文件路径
+     * @param headerSize 头信息字节数(必须2的倍数)
+     * @param signSize   签名信息字节数
+     * @return
+     */
+    public static String[] readFileHeader(String path, int headerSize, int signSize) {
+        int n = headerSize / 2;
+        String[] codes = new String[n + 1];
+
+        File file = new File(path);
+        try (
+                FileInputStream fis = new FileInputStream(file);
+                DataInputStream ois = new DataInputStream(fis);
+        ) {
+            //分n次读取文件(n * 2)个字节
+            for (int i = 0; i < n; i++) {
+                codes[i] = String.valueOf(ois.readShort());
+            }
+
+            if (signSize > 0) {
+                StringBuilder ss = new StringBuilder();
+                for (int i = 0; i < signSize; i++) {
+                    ss.append((char) ois.readByte());
+                }
+                codes[2] = ss.toString();
+            }
+        } catch (FileNotFoundException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return codes;
+    }
+
+    /**
+     * 读取文件内容
+     *
+     * @param file
+     * @return
+     */
+    public static String readFileContent(File file) {
+        StringBuilder content = new StringBuilder();
+        InputStreamReader streamReader = null;
+        BufferedReader bufferedReader = null;
+        try {
+            String encoding = "UTF-8";
+            if (file.exists() && file.isFile()) {
+                streamReader = new InputStreamReader(new FileInputStream(file), encoding);
+                bufferedReader = new BufferedReader(streamReader);
+                String line;
+                while ((line = bufferedReader.readLine()) != null) {
+                    content.append(line);
+                }
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(streamReader);
+            IOUtils.closeQuietly(bufferedReader);
+        }
+        return content.toString();
+    }
+
+    /**
+     * 在文件流前面追加头信息和签名信息,并生成新的“.tk”文件
+     */
+    public static boolean appendHeader(File file, short[] headers, String sign) {
+        if (file == null || !file.exists()) {
+            return false;
+        }
+
+        if (!file.isFile()) {
+            return false;
+        }
+
+        FileInputStream fis = null;
+        InputStream is = null;
+        FileOutputStream fos = null;
+        DataOutputStream dos = null;
+        try {
+            //创建临时文件
+            String baseFilePath = file.getAbsolutePath();
+            String targetFilePath = getFilePathName(baseFilePath) + ".tk";
+            File newFile = new File(targetFilePath);
+            fos = new FileOutputStream(newFile);
+            dos = new DataOutputStream(fos);
+
+            //写入头信息
+            for (short s : headers) {
+                dos.writeShort(s);
+            }
+            if (sign != null && !"".equals(sign)) {
+                //写入签名信息
+                dos.write(sign.getBytes("ISO-8859-1"));
+            }
+
+            //在临时文件中追加原始文件内容
+            fis = new FileInputStream(file);
+            is = new BufferedInputStream(fis);
+
+            byte buffer[] = new byte[4 * 1024];
+            int len;
+            while ((len = is.read(buffer)) != -1) {
+                dos.write(buffer, 0, len);
+            }
+            dos.flush();
+            return true;
+        } catch (FileNotFoundException e) {
+            log.error(e.getMessage(), e);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        } finally {
+            IOUtils.closeQuietly(is);
+            IOUtils.closeQuietly(fis);
+            IOUtils.closeQuietly(dos);
+            IOUtils.closeQuietly(fos);
+        }
+        return false;
+    }
+
+    /**
+     * 生成日期目录路径
+     */
+    public static String generateDateDir() {
+        return "/" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + "/";
+    }
+
+    public static String generateFileName() {
+        return UUID.randomUUID().toString().replaceAll("-", "");
+    }
+
+    public static String generateDateName() {
+        return new SimpleDateFormat("yyMMddHHmmss").format(new Date());
+    }
+
+    /**
+     * 获取文件后缀名(包含".")
+     */
+    public static String getFileSuffix(String fileName) {
+        if (fileName == null) {
+            return "";
+        }
+        int index = fileName.lastIndexOf(".");
+        if (index > -1) {
+            return fileName.substring(index).toLowerCase();
+        }
+        return "";
+    }
+
+    /**
+     * 获取无后缀的文件名
+     *
+     * @param fileName 示例:../xxx/abc.xx
+     * @return 示例:../xxx/abc
+     */
+    public static String getFilePathName(String fileName) {
+        if (fileName != null && fileName.length() > 0) {
+            int index = fileName.lastIndexOf(".");
+            if (index != -1) {
+                return fileName.substring(0, index);
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 创建文件目录
+     */
+    public static boolean makeDirs(String path) {
+        if (path == null || "".equals(path)) {
+            return false;
+        }
+        File folder = new File(path);
+        if (!folder.exists()) {
+            return folder.mkdirs();
+        }
+        return true;
+    }
+
+    /**
+     * 保存字符串到文件中
+     */
+    public static void saveAsFile(String path, String content) {
+        saveAsFile(path, content, null);
+    }
+
+    public static void saveAsFile(String path, String content, String encoding) {
+        if (path == null || content == null) {
+            return;
+        }
+
+        if (encoding == null) {
+            encoding = "UTF-8";
+        }
+
+        File file = new File(path);
+        if (!file.exists()) {
+            if (FileUtil.makeDirs(file.getParent())) {
+                boolean ok = IoUtils.createFile(file);
+                if (!ok) {
+                    throw new RuntimeException("文件创建失败!");
+                }
+            }
+        }
+
+        try (
+                FileOutputStream fos = new FileOutputStream(file);
+                OutputStreamWriter write = new OutputStreamWriter(fos, encoding);
+                BufferedWriter bw = new BufferedWriter(write);
+        ) {
+            bw.write(content);
+            bw.flush();
+            log.info("save as file success. " + path);
+        } catch (IOException e) {
+            log.error("save as file error. " + path);
+        }
+    }
+
+    /**
+     * 解压文件
+     *
+     * @param targetDir 解压目录
+     * @param zipFile   待解压的ZIP文件
+     */
+    public static List<File> unZip(File targetDir, File zipFile) {
+        if (targetDir == null) {
+            log.error("解压目录不能为空!");
+            return null;
+        }
+
+        if (zipFile == null) {
+            log.error("待解压的文件不能为空!");
+            return null;
+        }
+
+        if (!zipFile.exists()) {
+            log.error("待解压的文件不存在!" + zipFile.getAbsolutePath());
+            return null;
+        }
+
+        String zipName = zipFile.getName().toLowerCase();
+        if (zipFile.isDirectory() || zipName.indexOf(".zip") < 0) {
+            log.error("待解压的文件格式错误!");
+            return null;
+        }
+
+        if (!targetDir.exists()) {
+            targetDir.mkdir();
+        }
+
+        List<File> result = new LinkedList<>();
+
+        try (ZipFile zip = new ZipFile(zipFile, Charset.forName("UTF-8"));) {
+
+            Enumeration entries = zip.entries();
+            while (entries.hasMoreElements()) {
+                ZipEntry entry = (ZipEntry) entries.nextElement();
+
+                //Linux中需要替换掉路径的反斜杠
+                String entryName = (File.separator + entry.getName()).replaceAll("\\\\", "/");
+
+                String filePath = targetDir.getAbsolutePath() + entryName;
+                File target = new File(filePath);
+                if (entry.isDirectory()) {
+                    target.mkdirs();
+                } else {
+                    File dir = target.getParentFile();
+                    if (!dir.exists()) {
+                        dir.mkdirs();
+                    }
+
+                    try (OutputStream os = new FileOutputStream(target);
+                         InputStream is = zip.getInputStream(entry);) {
+                        IOUtils.copy(is, os);
+                        os.flush();
+                    } catch (IOException e) {
+                        log.error(e.getMessage(), e);
+                    }
+                    result.add(target);
+                }
+            }
+
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return result;
+    }
+
+    /**
+     * 文件压缩
+     *
+     * @param target  目录或文件
+     * @param zipFile 压缩后的ZIP文件
+     */
+    public static boolean doZip(File target, File zipFile) {
+        if (target == null || !target.exists()) {
+            log.error("目录或文件不能为空!");
+            return false;
+        }
+
+        if (zipFile == null) {
+            log.error("待压缩的文件不能为空!");
+            return false;
+        }
+
+        try (
+                OutputStream outStream = new FileOutputStream(zipFile);
+                ZipOutputStream zipOutStream = new ZipOutputStream(outStream, Charset.forName("UTF-8"));
+        ) {
+            if (!zipFile.exists()) {
+                boolean ok = zipFile.createNewFile();
+                if (!ok) {
+                    log.error("压缩的文件创建失败!");
+                    return false;
+                }
+            }
+
+            if (target.isDirectory()) {
+                File[] files = target.listFiles();
+                if (files.length == 0) {
+                    log.error("文件夹内未找到任何文件!");
+                    return false;
+                }
+
+                for (File file : files) {
+                    doZip(zipOutStream, file, null);
+                }
+            } else {
+                doZip(zipOutStream, target, null);
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+
+        return true;
+    }
+
+    private static void doZip(ZipOutputStream zipOutStream, File target, String parentDir) throws IOException {
+        //log.info("Zip:" + parentDir);
+        if (parentDir == null) {
+            parentDir = "";
+        }
+
+        if (!"".equals(parentDir) && !parentDir.endsWith(File.separator)) {
+            parentDir += File.separator;
+        }
+
+        if (target.isDirectory()) {
+            File[] files = target.listFiles();
+            if (files.length > 0) {
+                for (File file : files) {
+                    doZip(zipOutStream, file, parentDir + target.getName());
+                }
+            } else {
+                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
+                zipOutStream.closeEntry();
+            }
+        } else {
+            try (InputStream is = new FileInputStream(target);) {
+                zipOutStream.putNextEntry(new ZipEntry(parentDir + target.getName()));
+                int len;
+                byte[] bytes = new byte[1024];
+                while ((len = is.read(bytes)) > 0) {
+                    zipOutStream.write(bytes, 0, len);
+                }
+            } catch (IOException e) {
+                log.error(e.getMessage(), e);
+            }
+            zipOutStream.closeEntry();
+        }
+    }
+    public static void deleteFolder(String path) {
+
+		File file = new File(path);
+		if (file.exists()) {
+			if (file.isFile()) {
+				deleteFile(path);
+			} else {
+				deleteDirectory(path);
+			}
+		}
+	}
+
+	public static void deleteFile(String path) {
+		File file = new File(path);
+		if (file.isFile() && file.exists()) {
+			file.delete();
+		}
+	}
+
+	public static void deleteDirectory(String path) {
+		if (!path.endsWith(File.separator)) {
+			path = path + File.separator;
+		}
+		File dirFile = new File(path);
+		if (!dirFile.exists() || !dirFile.isDirectory()) {
+			return;
+		}
+		File[] files = dirFile.listFiles();
+		if (files != null) {
+			for (int i = 0; i < files.length; i++) {
+				if (files[i].isFile()) {
+					deleteFile(files[i].getAbsolutePath());
+				} else {
+					deleteDirectory(files[i].getAbsolutePath());
+				}
+			}
+		}
+
+		dirFile.delete();
+	}
+}

+ 224 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/JsonMapper.java

@@ -0,0 +1,224 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:09.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.utils;
+
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.*;
+import com.fasterxml.jackson.databind.util.JSONPObject;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 简单封装Jackson,实现JSON 与 Java Object互相转换的Mapper
+ * 封装不同的输出风格, 使用不同的builder函数创建实例
+ */
+@SuppressWarnings("unchecked")
+public class JsonMapper {
+    private static Logger log = LoggerFactory.getLogger(JsonMapper.class);
+    private ObjectMapper mapper;
+
+    public JsonMapper() {
+        this(null);
+    }
+
+    public JsonMapper(Include include) {
+        mapper = new ObjectMapper();
+        //设置输出时包含属性的风格
+        if (include != null) {
+            mapper.setSerializationInclusion(include);
+        }
+        //设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
+        mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
+    }
+
+    /**
+     * 创建只输出非Null且非Empty(如List.isEmpty)的属性到Json字符串的Mapper,建议在外部接口中使用
+     */
+    public static JsonMapper nonEmptyMapper() {
+        return new JsonMapper(Include.NON_EMPTY);
+    }
+
+    public static JsonMapper nonNullMapper() {
+        return new JsonMapper(Include.NON_NULL);
+    }
+
+    /**
+     * 创建只输出初始值被改变的属性到Json字符串的Mapper, 最节约的存储方式,建议在内部接口中使用
+     */
+    public static JsonMapper nonDefaultMapper() {
+        return new JsonMapper(Include.NON_DEFAULT);
+    }
+
+    /**
+     * Object可以是POJO,也可以是Collection或数组
+     * 如果对象为Null, 返回"null"
+     * 如果集合为空集合, 返回"[]"
+     */
+    public String toJson(Object object) {
+        try {
+            return mapper.writeValueAsString(object);
+        } catch (IOException e) {
+            log.error("write to json string error:" + object);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化POJO或简单Collection如List<String>
+     * 如果JSON字符串为Null或"null"字符串, 返回Null
+     * 如果JSON字符串为"[]", 返回空集合
+     * 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String, JavaType)
+     */
+    public <T> T fromJson(String jsonString, Class<T> clazz) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return mapper.readValue(jsonString, clazz);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化复杂Collection如List<Bean>, 先使用createCollectionType()或constructMapType()构造类型, 然后调用本函数
+     */
+    public <T> T fromJson(String jsonString, JavaType javaType) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return (T) mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * 反序列化复杂的对象,如Page<Bean>
+     */
+    public <T> T fromJson(String jsonString, TypeReference javaType) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            return (T) mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * json to list
+     */
+    public <T> List<T> toList(String jsonString, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JavaType javaType = constructCollectionType(List.class, bean);
+            return mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error", e);
+            return null;
+        }
+    }
+
+    /**
+     * json to simple HashMap
+     */
+    public <T> Map<String, T> toHashMap(String jsonString, Class<T> bean) {
+        if (StringUtils.isEmpty(jsonString)) {
+            return null;
+        }
+        try {
+            JavaType javaType = constructMapType(HashMap.class, String.class, bean);
+            return mapper.readValue(jsonString, javaType);
+        } catch (IOException e) {
+            log.error("parse json string error:", e);
+            return null;
+        }
+    }
+
+    /**
+     * 构造Collection类型
+     */
+    public JavaType constructCollectionType(Class<? extends Collection> collectionClass, Class<?> elementClass) {
+        return mapper.getTypeFactory().constructCollectionType(collectionClass, elementClass);
+    }
+
+    /**
+     * 构造Map类型
+     */
+    public JavaType constructMapType(Class<? extends Map> mapClass, Class<?> keyClass, Class<?> valueClass) {
+        return mapper.getTypeFactory().constructMapType(mapClass, keyClass, valueClass);
+    }
+
+    /**
+     * 当JSON里只含有Bean的部分屬性時,更新一個已存在Bean,只覆盖該部分的屬性
+     */
+    public void update(String jsonString, Object object) {
+        try {
+            mapper.readerForUpdating(object).readValue(jsonString);
+        } catch (JsonProcessingException e) {
+            log.error("update json string:" + jsonString + " to object:" + object + " error.");
+        } catch (IOException e) {
+            log.error("update json string:" + jsonString + " to object:" + object + " error.");
+        }
+    }
+
+    /**
+     * 輸出JSONP格式数据
+     */
+    public String toJsonP(String functionName, Object object) {
+        return toJson(new JSONPObject(functionName, object));
+    }
+
+    /**
+     * 設定是否使用Enum的toString函數來读写Enum
+     * 為False時使用Enum的name()函數來读写Enum, 默認為False
+     * 注意本函數一定要在Mapper創建後, 所有的读写動作之前調用
+     */
+    public void enableEnumUseToString() {
+        mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
+        mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
+    }
+
+    /**
+     * 取出Mapper做进一步的设置或使用其他序列化API
+     */
+    public ObjectMapper getMapper() {
+        return mapper;
+    }
+
+    /***
+     * 把Json字符串转换成Node对象
+     */
+    public JsonNode getNode(String jsonStr) {
+        try {
+            //mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
+            return mapper.readTree(jsonStr);
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return null;
+        }
+    }
+
+}

+ 44 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/converter/utils/NumUtil.java

@@ -0,0 +1,44 @@
+/*
+ * *************************************************
+ * Copyright (c) 2018 QMTH. All Rights Reserved.
+ * Created by Deason on 2018-07-12 15:31:10.
+ * *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.converter.utils;
+
+public class NumUtil {
+    private static final String[] CHAR = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
+
+    /**
+     * 将(1-26)区间的数字转换成字母
+     */
+    public static String toEnNumber(int number) {
+        String result = "";
+        if (number < 1 || number > 26) {
+            //限制支持的数字范围
+            return result;
+        }
+        return CHAR[number - 1];
+    }
+
+    /**
+     * 将字符串内的字母转换成(1-26)区间的数字
+     */
+    public static String toNumber(String character) {
+        if (character == null) {
+            return "";
+        }
+        StringBuilder str = new StringBuilder();
+        char[] chars = character.toUpperCase().toCharArray();
+        for (char c : chars) {
+            if (String.valueOf(c).matches("[a-zA-Z]")) {
+                str.append(c - 64);
+            } else {
+                str.append(c);
+            }
+        }
+        return str.toString();
+    }
+
+}

+ 71 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/CourseSpeciatly.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.core.questions.base.core;
+
+import java.io.Serializable;
+import java.util.Date;
+
+
+public class CourseSpeciatly implements Serializable {
+
+    private static final long serialVersionUID = -250814769612993025L;
+
+    private Long id;
+
+    private Long courseId;
+
+    private Long specialtyId;
+
+    private Long creator;
+
+    private Date createTime;
+
+    private Date updateTime;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Long getCourseId() {
+        return courseId;
+    }
+
+    public void setCourseId(Long courseId) {
+        this.courseId = courseId;
+    }
+
+    public Long getSpecialtyId() {
+        return specialtyId;
+    }
+
+    public void setSpecialtyId(Long specialtyId) {
+        this.specialtyId = specialtyId;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Date getUpdateTime() {
+        return updateTime;
+    }
+
+    public Long getCreator() {
+        return creator;
+    }
+
+    public void setCreator(Long creator) {
+        this.creator = creator;
+    }
+
+    public void setUpdateTime(Date updateTime) {
+        this.updateTime = updateTime;
+    }
+
+}

+ 92 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/CourseSync.java

@@ -0,0 +1,92 @@
+package cn.com.qmth.examcloud.core.questions.base.core;
+
+import java.io.Serializable;
+
+/**
+ * Created by songyue on 17/8/3.
+ */
+public class CourseSync implements Serializable {
+
+    private static final long serialVersionUID = 8986880538637117108L;
+
+    private Long id;
+
+    private String code;
+
+    private String name;
+
+    private Long orgId;
+
+    private String level;
+
+    private String createTime;
+
+    private String updateTime;
+
+    private Boolean enable;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getLevel() {
+        return level;
+    }
+
+    public void setLevel(String level) {
+        this.level = level;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(String updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+}

+ 70 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/ExamCourseDto.java

@@ -0,0 +1,70 @@
+package cn.com.qmth.examcloud.core.questions.base.core;
+
+import cn.com.qmth.examcloud.core.questions.base.em.enums.ExamType;
+
+/**
+ * @author chenken
+ * @date 2017年7月20日 上午9:49:03
+ * @company QMTH
+ * @description ExamCourseDto.java
+ */
+public class ExamCourseDto {
+    private Long examId;
+    private String examName;
+    private ExamType examType;
+    private String courseName;
+    private String courseCode;
+    private Long orgId;
+
+    public ExamCourseDto() {
+    }
+
+    public Long getExamId() {
+        return examId;
+    }
+
+    public void setExamId(Long examId) {
+        this.examId = examId;
+    }
+
+    public String getExamName() {
+        return examName;
+    }
+
+    public void setExamName(String examName) {
+        this.examName = examName;
+    }
+
+    public ExamType getExamType() {
+        return examType;
+    }
+
+    public void setExamType(ExamType examType) {
+        this.examType = examType;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+    public String getCourseCode() {
+        return courseCode;
+    }
+
+    public void setCourseCode(String courseCode) {
+        this.courseCode = courseCode;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+}
+

+ 57 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/core/enums/CourseLevel.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.examcloud.core.questions.base.core.enums;
+
+public enum CourseLevel {
+
+    ZSB("专升本", "Z"),
+
+    GQZ("高起专", "Q"),
+
+    GQB("高起本", "S"),
+
+    ALL("不限", "");
+
+    /**
+     * 名称
+     */
+    private String name;
+
+    /**
+     * 简写
+     */
+    private String abbreviate;
+
+    private CourseLevel(String name, String abbreviate) {
+        this.name = name;
+        this.abbreviate = abbreviate;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getAbbreviate() {
+        return abbreviate;
+    }
+
+    /**
+     * 混合查询
+     *
+     * @param name
+     * @return
+     * @author WANGWEI
+     */
+    public static CourseLevel getCourseLevel(String name) {
+        if (null == name) {
+            return CourseLevel.ALL;
+        }
+        for (CourseLevel cur : CourseLevel.values()) {
+            if (name.equals(cur.getName())) {
+                return cur;
+            }
+            if (name.equals(cur.name())) {
+                return cur;
+            }
+        }
+        return CourseLevel.ALL;
+    }
+}

+ 82 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/dto/ExportTempDataDto.java

@@ -0,0 +1,82 @@
+package cn.com.qmth.examcloud.core.questions.base.dto;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import cn.com.qmth.examcloud.core.questions.base.json.SectionElement;
+
+public class ExportTempDataDto {
+
+    private int answerNum = 0;
+
+    private int mainNum = 0;
+
+    private int subNum = 0;
+
+    private int grandNum = 0;
+
+    private int index = 0;
+
+    private Set<String> types = new HashSet<String>();
+
+    private List<SectionElement> images = new ArrayList<SectionElement>();
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+    public Set<String> getTypes() {
+        return types;
+    }
+
+    public void setTypes(Set<String> types) {
+        this.types = types;
+    }
+
+    public List<SectionElement> getImages() {
+        return images;
+    }
+
+    public void setImages(List<SectionElement> images) {
+        this.images = images;
+    }
+
+    public int getMainNum() {
+        return mainNum;
+    }
+
+    public void setMainNum(int mainNum) {
+        this.mainNum = mainNum;
+    }
+
+    public int getSubNum() {
+        return subNum;
+    }
+
+    public void setSubNum(int subNum) {
+        this.subNum = subNum;
+    }
+
+    public int getGrandNum() {
+        return grandNum;
+    }
+
+    public void setGrandNum(int grandNum) {
+        this.grandNum = grandNum;
+    }
+
+    public int getAnswerNum() {
+        return answerNum;
+    }
+
+    public void setAnswerNum(int answerNum) {
+        this.answerNum = answerNum;
+    }
+
+}

+ 139 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/dto/ExportTemplateDto.java

@@ -0,0 +1,139 @@
+package cn.com.qmth.examcloud.core.questions.base.dto;
+
+import java.util.Date;
+
+public class ExportTemplateDto {
+
+    private Long id;
+
+    private String rootOrgId;
+
+    private String type;
+
+    private String typeName;
+
+    private String fileName;
+    
+    private String originalFileName;
+
+    private String suffix;
+
+    private String filePath;
+
+    private String fileKey;
+
+    private Boolean enable;
+
+    private Date creationTime;
+
+    private String createUser;
+
+    private String fullFilePath;
+
+    public String getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(String rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public String getFileName() {
+        return fileName;
+    }
+
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    public String getSuffix() {
+        return suffix;
+    }
+
+    public void setSuffix(String suffix) {
+        this.suffix = suffix;
+    }
+
+    public String getFilePath() {
+        return filePath;
+    }
+
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    public String getFileKey() {
+        return fileKey;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setFileKey(String fileKey) {
+        this.fileKey = fileKey;
+    }
+
+    public Boolean getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Boolean enable) {
+        this.enable = enable;
+    }
+
+    public Date getCreationTime() {
+        return creationTime;
+    }
+
+    public void setCreationTime(Date creationTime) {
+        this.creationTime = creationTime;
+    }
+
+    public String getCreateUser() {
+        return createUser;
+    }
+
+    public void setCreateUser(String createUser) {
+        this.createUser = createUser;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getFullFilePath() {
+        return fullFilePath;
+    }
+
+    public void setFullFilePath(String fullFilePath) {
+        this.fullFilePath = fullFilePath;
+    }
+
+    public String getTypeName() {
+        return typeName;
+    }
+
+    public void setTypeName(String typeName) {
+        this.typeName = typeName;
+    }
+
+    
+    public String getOriginalFileName() {
+        return originalFileName;
+    }
+
+    
+    public void setOriginalFileName(String originalFileName) {
+        this.originalFileName = originalFileName;
+    }
+
+}

+ 41 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/em/enums/ExamType.java

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.core.questions.base.em.enums;
+
+/**
+ * @author ting.yin
+ * @Description: 考试类型
+ * @date 2017年1月5日
+ */
+public enum ExamType {
+    /**
+     * 传统考试
+     */
+    TRADITION,
+    /**
+     * 在线考试
+     */
+    ONLINE,
+    /**
+     * 在线练习
+     */
+    PRACTICE,
+
+    /**
+     * 离线考试
+     */
+    OFFLINE,
+
+    /**
+     * 分布式印刷考试
+     */
+    PRINT_EXAM;
+
+    public static ExamType strToEnum(String str) {
+        for (ExamType examType : ExamType.values()) {
+            if (examType.name().equals(str)) {
+                return examType;
+            }
+        }
+        return null;
+    }
+
+}

+ 19 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/AudioPositionType.java

@@ -0,0 +1,19 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * @author chenken
+ * @date 2017年8月2日 下午1:53:30
+ * @company QMTH
+ * @description AudioPositionType.java
+ */
+public enum AudioPositionType {
+    /**
+     * 题干
+     */
+    QUESTION_BODY,
+    /**
+     * 选项
+     */
+    QUESTION_OPTION
+}
+

+ 53 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExamFileType.java

@@ -0,0 +1,53 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * @author chenken
+ * @date 2017年7月13日 下午4:34:29
+ * @company QMTH
+ * @description 考试 文件类型
+ */
+public enum ExamFileType {
+    /**
+     * 试卷
+     */
+    PAPER("试卷"),
+    /**
+     * 答案
+     */
+    ANSWER("答案"),
+    /**
+     * ZIP文件
+     * 试卷结构
+     * 主观题
+     */
+    PAPER_STRUCTURE_SUBJECTIVE("主观题"),
+    /**
+     * ZIP文件
+     * 试卷结构
+     * 客观题
+     */
+    PAPER_STRUCTURE_OBJECTIVE("客观题"),
+    /**
+     * 音频文件
+     */
+    AUDIO("音频文件"),
+    /**
+     * 机考数据包
+     */
+    COMPUTERTEST_PACKAGE("机考数据包"),
+    /**
+     * 分布式印刷数据包
+     */
+    PRINT_EXAM_PACKAGE("分布式印刷数据包");
+
+    private String name;
+
+    ExamFileType(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+}

+ 62 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExportTemplateType.java

@@ -0,0 +1,62 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+import org.apache.commons.lang3.StringUtils;
+
+public enum ExportTemplateType {
+
+    PAPER_EXPORT("PAPER_EXPORT", "试卷导出"),
+
+    ANWSER_EXPORT("ANWSER_EXPORT", "答案导出"), OUTLINE_PAPER_EXPORT("OUTLINE_PAPER_EXPORT",
+            "离线试卷"), PAPER_VIEW("PAPER_VIEW", "试卷预览"),
+
+    ANWSER_VIEW("ANWSER_VIEW", "答案预览");
+
+    private ExportTemplateType(String code, String name) {
+        this.code = code;
+        this.name = name;
+    }
+
+    private String code;
+
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    ExportTemplateType(String name) {
+        this.name = name;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public static boolean checkCode(String v) {
+        if (StringUtils.isBlank(v)) {
+            return false;
+        }
+        for (ExportTemplateType e : ExportTemplateType.values()) {
+            if (e.getCode().equals(v)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static String getName(String v) {
+        if (StringUtils.isBlank(v)) {
+            return null;
+        }
+        for (ExportTemplateType e : ExportTemplateType.values()) {
+            if (e.getCode().equals(v)) {
+                return e.getName();
+            }
+        }
+        return null;
+    }
+}

+ 32 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExportType.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * @author chenken
+ * @date 2017年7月18日 上午10:21:48
+ * @company QMTH
+ * @description 试卷导出类型
+ */
+public enum ExportType {
+
+    /**
+     * 机考
+     */
+    ONLINE("机考"),
+
+    /**
+     * 普通
+     */
+    NORMAL("普通");
+
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    ExportType(String name) {
+        this.name = name;
+    }
+
+}
+

+ 28 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExportWay.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * @author chenken
+ * @date 2017年7月20日 上午8:59:58
+ * @company QMTH
+ * @description ExportWay.java
+ */
+public enum ExportWay {
+    /**
+     * 批量导出
+     */
+    BATCH,
+    /**
+     * 单独导出
+     */
+    SINGLE;
+
+    public static ExportWay strToEnum(String str) {
+        for (ExportWay exportWay : ExportWay.values()) {
+            if (exportWay.name().equals(str)) {
+                return exportWay;
+            }
+        }
+        return null;
+    }
+}
+

+ 23 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/ExtractPolicy.java

@@ -0,0 +1,23 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum ExtractPolicy {
+
+    RANDOM_POLICY("RANDOM_PAPER", "随机抽卷");
+
+    private String key;
+    private String value;
+
+    ExtractPolicy(String key, String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+}

+ 28 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/GenPaperExpression.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum GenPaperExpression {
+    EQUAL(1L, "等于"),
+    NOT_EQUAL(2L, "不等于"),
+    LESS(3L, "小于"),
+    LESS_EQUAL(4L, "小于等于"),
+    GREATER(5L, "大于"),
+    GREATER_EQUAL(6L, "大于等于");
+
+    private Long key;
+    private String name;
+
+    GenPaperExpression(Long key, String name) {
+        this.key = key;
+        this.name = name;
+    }
+
+
+    public Long getKey() {
+        return key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+}

+ 24 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/GenPaperFunction.java

@@ -0,0 +1,24 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum GenPaperFunction {
+    SUM(1L, "求和"),
+    RATIO(2L, "比例");
+
+    private Long key;
+    private String name;
+
+    GenPaperFunction(Long key, String name) {
+        this.key = key;
+        this.name = name;
+    }
+
+    public Long getKey() {
+        return key;
+    }
+
+
+    public String getName() {
+        return name;
+    }
+
+}

+ 30 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/GenPaperType.java

@@ -0,0 +1,30 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum GenPaperType {
+
+    /**
+     * 细节组卷
+     */
+    SPECIFIC("细节"),
+
+    /**
+     * 宏观
+     */
+    ENSEMBLE("宏观"),
+
+    /**
+     * 普通组卷
+     */
+    COMMON("普通组卷");
+
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    GenPaperType(String name) {
+        this.name = name;
+    }
+
+}

+ 32 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperStatus.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * Created by songyue on 17/3/15.
+ */
+public enum PaperStatus {
+
+    DRAFT(1L, "待审核"),
+    PASS(2L, "通过"),
+    NOPASS(3L, "不通过");
+
+    private Long id;
+    private String name;
+
+    PaperStatus(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

+ 44 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperStructType.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * @author weiwenhai
+ * @describle 试卷结构类型
+ * @date 2017.12.11
+ */
+public enum PaperStructType {
+
+    /**
+     * 简易组卷
+     */
+    SIMPLE("简易组卷"),
+
+    /**
+     * 精确组卷
+     */
+    EXACT("精确组卷"),
+
+    /**
+     * 蓝图组卷
+     */
+    BLUEPRINT("蓝图组卷");
+
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    PaperStructType(String name) {
+        this.name = name;
+    }
+
+    public static PaperStructType strToEnum(String str) {
+        for (PaperStructType paperStructType : PaperStructType.values()) {
+            if (paperStructType.name().equals(str)) {
+                return paperStructType;
+            }
+        }
+        return null;
+    }
+
+}

+ 44 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperType.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * Created by songyue on 17/3/15.
+ */
+public enum PaperType {
+    /**
+     * 导入
+     */
+    IMPORT(1L, "导入"),
+    /**
+     * 组卷
+     */
+    GENERATE(2L, "组卷"),
+    /**
+     * 调卷规则预览卷
+     */
+    PREVIEW(3L, "调卷规则预览卷"),
+    /**
+     * 考生考卷
+     */
+    STUDENT_EXAM(4L, "考生考卷");
+
+    private Long id;
+    private String name;
+
+    PaperType(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

+ 41 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PropertyDifficulty.java

@@ -0,0 +1,41 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum PropertyDifficulty {
+
+    /*
+     * 简单
+     */
+    SIMPLE(1L, "简单"),
+
+    /**
+     * 中等
+     */
+    MEDIUM(2L, "中等"),
+
+    /**
+     * 困难
+     */
+    DIFFICULTY(3L, "困难");
+
+    private Long id;
+
+    private String name;
+
+    PropertyDifficulty(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

+ 32 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/QuesUnit.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * Created by songyue on 17/3/15.
+ */
+public enum QuesUnit {
+
+    QUES_BODY(1L, "题干"),
+    QUES_OPTION(2L, "选项"),
+    QUES_ANSWER(3L, "答案");
+
+    private Long id;
+    private String name;
+
+    QuesUnit(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

+ 23 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/RandomGenPaperPolicy.java

@@ -0,0 +1,23 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum RandomGenPaperPolicy {
+    BY_QUESTIONNUM(1L, "按每套试卷需要抽取的试题数量来组卷"),
+    BY_SCORE(2L, "按每套试卷需要抽取的分数来组卷");
+
+    private Long key;
+    private String value;
+
+    RandomGenPaperPolicy(Long key, String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    public Long getKey() {
+        return key;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+}

+ 31 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/Switch.java

@@ -0,0 +1,31 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+/**
+ * Created by songyue on 17/3/27.
+ */
+public enum Switch {
+
+    ON(1L, "开启"),
+    OFF(2L, "关闭");
+
+    private Long id;
+    private String name;
+
+    Switch(Long id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+}

+ 57 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ColumnSetting.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.examcloud.core.questions.base.excel;
+
+public class ColumnSetting implements Comparable<ColumnSetting> {
+
+    private String header;
+    private String fieldName;
+    private int width;
+    private int index;
+
+    public ColumnSetting(String header, String fieldName, int width, int index) {
+        this.header = header;
+        this.fieldName = fieldName;
+        this.width = width;
+        this.index = index;
+    }
+
+    public String getHeader() {
+        return header;
+    }
+
+    public void setHeader(String header) {
+        this.header = header;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public int getIndex() {
+        return index;
+    }
+
+    public void setIndex(int index) {
+        this.index = index;
+    }
+
+    public String getFieldName() {
+        return fieldName;
+    }
+
+    public void setFieldName(String fieldName) {
+        this.fieldName = fieldName;
+    }
+
+    @Override
+    public int compareTo(ColumnSetting columnSetting) {
+        if (index < columnSetting.getIndex())
+            return -1;
+        if (index > columnSetting.getIndex())
+            return 1;
+        return 0;
+    }
+}

+ 49 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelError.java

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.core.questions.base.excel;
+
+/**
+ * @author ting.yin
+ * @Description: excel导入错误
+ * @date 2016年8月19日
+ */
+public class ExcelError {
+
+    /**
+     * 错误行数
+     */
+    private int row;
+
+    /**
+     * 错误类型
+     */
+    private String excelErrorType;
+
+    public ExcelError() {
+
+    }
+
+    public ExcelError(int row, String excelErrorType) {
+        this.row = row;
+        this.excelErrorType = excelErrorType;
+    }
+
+    public ExcelError(String excelErrorType) {
+        this.excelErrorType = excelErrorType;
+    }
+
+    public int getRow() {
+        return row;
+    }
+
+    public void setRow(int row) {
+        this.row = row;
+    }
+
+    public String getExcelErrorType() {
+        return excelErrorType;
+    }
+
+    public void setExcelErrorType(String excelErrorType) {
+        this.excelErrorType = excelErrorType;
+    }
+
+}

+ 33 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelProperty.java

@@ -0,0 +1,33 @@
+package cn.com.qmth.examcloud.core.questions.base.excel;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelProperty {
+    /**
+     * 导出时列名
+     */
+    String name() default "";
+
+    /**
+     * 导出时列宽
+     */
+    int width() default 0;
+
+    /**
+     * 排序
+     */
+    int index();
+
+    /**
+     * 类型
+     * 0:导入(读excel)
+     * 1:导出(写excel)
+     * 2:导入&导出
+     */
+    int type() default 2;
+}

+ 165 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelReader.java

@@ -0,0 +1,165 @@
+package cn.com.qmth.examcloud.core.questions.base.excel;
+
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import org.apache.commons.io.IOUtils;
+import org.apache.poi.ss.usermodel.*;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * @author ting.yin
+ * @Description: 读取excel
+ * @date 2016年8月19日
+ */
+public class ExcelReader extends ExcelUtils {
+
+    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+    public ExcelReader(Class<?> dataClass) {
+        super(dataClass, true);
+    }
+
+    /**
+     * @param inputStream 输入流
+     * @param handle      单个对象处理器
+     * @return 错误信息集合
+     */
+    public List<ExcelError> reader(InputStream inputStream, ExcelReaderHandle handle) {
+        List<ExcelError> excelErrors = new ArrayList<>();
+        Workbook wb = null;
+        try {
+            wb = WorkbookFactory.create(inputStream);
+            Sheet sheet = wb.getSheetAt(0);
+            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+                Row row = sheet.getRow(i);
+                if (row == null) {
+                    return excelErrors;
+                }
+                Object dto = getDataClass().newInstance();
+                for (int j = 0; j < this.getColumnSettings().size(); j++) {
+                    ColumnSetting columnSetting = this.getColumnSettings().get(j);
+                    Cell cell = row.getCell(columnSetting.getIndex());
+                    Field field = getDataClass().getDeclaredField(columnSetting.getFieldName());
+                    Object obj = convert(cell, field);
+                    field.setAccessible(true);
+                    field.set(dto, obj);
+                }
+                ExcelError error = handle.handle(dto);
+                if (error != null) {
+                    error.setRow(i + 1);
+                    excelErrors.add(error);
+                }
+            }
+        } catch (StatusException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new StatusException("580", "EXCEL导入失败", e);
+        } finally {
+            IOUtils.closeQuietly(wb);
+            IOUtils.closeQuietly(inputStream);
+        }
+        return excelErrors;
+    }
+
+    @SuppressWarnings("deprecation")
+    private Object convert(Cell cell, Field field) {
+        if (cell != null) {
+            switch (cell.getCellType()) {
+                case Cell.CELL_TYPE_STRING:
+                    return cell.getStringCellValue().toString();
+                case Cell.CELL_TYPE_BOOLEAN:
+                    return String.valueOf(cell.getBooleanCellValue());
+                case Cell.CELL_TYPE_NUMERIC:
+                    return processNumeric(cell, field);
+            }
+        }
+
+        return null;
+    }
+
+    private Object processNumeric(Cell cell, Field field) {
+
+        if (DateUtil.isCellDateFormatted(cell)) {
+
+            if (field.getType().isAssignableFrom(Date.class)) {
+                return cell.getDateCellValue();
+            } else {
+                return dateFormat.format(cell.getDateCellValue());
+            }
+
+        } else {
+
+            if (field.getType().isAssignableFrom(Integer.class)) {
+
+                return (int) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(int.class)) {
+
+                return (int) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(Long.class)) {
+
+                return (long) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(long.class)) {
+
+                return (long) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(Short.class)) {
+
+                return (short) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(short.class)) {
+
+                return (short) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(Float.class)) {
+
+                return (float) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(float.class)) {
+
+                return (float) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(Byte.class)) {
+
+                return (byte) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(byte.class)) {
+
+                return (byte) cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(Double.class)) {
+
+                return cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(double.class)) {
+
+                return cell.getNumericCellValue();
+
+            } else if (field.getType().isAssignableFrom(String.class)) {
+
+                String numStr = String.valueOf(cell.getNumericCellValue());
+                if (numStr.contains("E")) {
+                    numStr = new BigDecimal(numStr.trim()).toPlainString();
+                }
+
+                if (numStr.endsWith(".0")) {
+                    numStr = numStr.substring(0, numStr.indexOf(".0"));
+                }
+
+                return numStr;
+            } else {
+                return cell.getNumericCellValue();
+            }
+        }
+    }
+
+}

+ 7 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelReaderHandle.java

@@ -0,0 +1,7 @@
+package cn.com.qmth.examcloud.core.questions.base.excel;
+
+public interface ExcelReaderHandle {
+
+    ExcelError handle(Object dto);
+
+}

+ 71 - 0
examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/excel/ExcelUtils.java

@@ -0,0 +1,71 @@
+package cn.com.qmth.examcloud.core.questions.base.excel;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Created by zhengmin on 2016/8/17.
+ */
+@SuppressWarnings("rawtypes")
+public abstract class ExcelUtils {
+
+    private Class dataClass;
+    private List<ColumnSetting> columnSettings;
+
+    public ExcelUtils(Class<?> dataClass, boolean isRead) {
+        this.dataClass = dataClass;
+        this.columnSettings = getColumnSettings(dataClass, isRead);
+    }
+
+    public Class getDataClass() {
+        return dataClass;
+    }
+
+    public void setDataClass(Class dataClass) {
+        this.dataClass = dataClass;
+    }
+
+    public List<ColumnSetting> getColumnSettings() {
+        return columnSettings;
+    }
+
+    public void setColumnSettings(List<ColumnSetting> columnSettings) {
+        this.columnSettings = columnSettings;
+    }
+
+    /**
+     * 提取ExcelProperty注解类的字段信息
+     *
+     * @param dataClass 需要解析excel的数据类型
+     * @return
+     */
+    protected List<ColumnSetting> getColumnSettings(Class<?> dataClass, boolean isRead) {
+        List<ColumnSetting> columnSettings = new ArrayList<>();
+        Field[] fileds = dataClass.getDeclaredFields();
+        for (Field field : fileds) {
+            ExcelProperty exportProperty = field.getAnnotation(ExcelProperty.class);
+            if (exportProperty != null) {
+                if (isRead) {
+                    if (exportProperty.type() == 0 || exportProperty.type() == 2) {
+                        ColumnSetting columnSetting = new ColumnSetting(
+                                exportProperty.name(), field.getName(), exportProperty.width(),
+                                exportProperty.index());
+                        columnSettings.add(columnSetting);
+                    }
+                } else {
+                    if (exportProperty.type() == 1 || exportProperty.type() == 2) {
+                        ColumnSetting columnSetting = new ColumnSetting(
+                                exportProperty.name(), field.getName(), exportProperty.width(),
+                                exportProperty.index());
+                        columnSettings.add(columnSetting);
+                    }
+                }
+            }
+        }
+        Collections.sort(columnSettings);
+        return columnSettings;
+    }
+
+}

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff