Parcourir la source

Merge remote-tracking branch 'origin/release_v5.0.1'

deason il y a 1 an
Parent
commit
7668b4e50b
80 fichiers modifiés avec 9492 ajouts et 5172 suppressions
  1. 26 34
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/CopyDataController.java
  2. 12 2
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/DemoController.java
  3. 300 283
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/ExtractConfigController.java
  4. 58 38
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperController.java
  5. 10 3
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/PaperStructController.java
  6. 150 142
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/QuesController.java
  7. 98 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/controller/RandomPaperController.java
  8. 20 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/ExtractConfigCloudServiceProvider.java
  9. 10 8
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/PaperCloudServiceProvider.java
  10. 0 72
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/QuestionCloudServiceProvider.java
  11. 52 0
      examcloud-core-questions-api-provider/src/main/java/cn/com/qmth/examcloud/core/questions/api/provider/RandomPaperCloudServiceProvider.java
  12. 14 31
      examcloud-core-questions-base/pom.xml
  13. 45 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/bean/PagerQuery.java
  14. 0 3
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/PaperType.java
  15. 16 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/base/enums/QuestionDifficulty.java
  16. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/config/MongoConfig.java
  17. 44 0
      examcloud-core-questions-base/src/main/java/cn/com/qmth/examcloud/core/questions/config/MyDbRefResolver.java
  18. 17 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/RandomPaperQuestionRepo.java
  19. 13 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/RandomPaperRepo.java
  20. 247 238
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/ExtractConfig.java
  21. 10 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/PaperSearchInfo.java
  22. 96 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/RandomPaper.java
  23. 126 0
      examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/RandomPaperQuestion.java
  24. 5 0
      examcloud-core-questions-service/pom.xml
  25. 2 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ExtractConfigService.java
  26. 4 7
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportDdCollegePaperService.java
  27. 2 5
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportPaperFromJsonService.java
  28. 3 3
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportPaperService.java
  29. 2 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperDetailUnitService.java
  30. 3 1
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperService.java
  31. 149 73
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperStructService.java
  32. 2 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/QuestionAudioService.java
  33. 12 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/RandomPaperQuestionService.java
  34. 34 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/RandomPaperService.java
  35. 14 5
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/context/DetailContext.java
  36. 12 7
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/context/PaperContext.java
  37. 5 4
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/context/UnitContext.java
  38. 28 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/dto/PaperDetailUnitDto.java
  39. 83 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/CreateDefaultPaperParam.java
  40. 95 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperDetailDto.java
  41. 115 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperDetailUnitDto.java
  42. 21 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperQuestionViewQuery.java
  43. 26 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperVo.java
  44. 22 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/QuesOptionDto.java
  45. 156 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/QuestionDto.java
  46. 99 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperDomain.java
  47. 183 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperListVo.java
  48. 75 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperQuery.java
  49. 47 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperQuestionDto.java
  50. 49 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructInfo.java
  51. 130 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructQuestionCheckDto.java
  52. 60 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructQuestionCountInfo.java
  53. 67 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructQuestionInfo.java
  54. 3 1
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/BasePaperCache.java
  55. 0 6
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/Constants.java
  56. 2 1
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/ExtractConfigCache.java
  57. 7 6
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/ExtractConfigPaperCache.java
  58. 192 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/PaperDetailStructCache.java
  59. 216 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/PaperStructCache.java
  60. 3 1
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/QuestionAnswerCache.java
  61. 7 6
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/QuestionCache.java
  62. 44 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/RandomPaperCache.java
  63. 2 2
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExportPaperServiceImpl.java
  64. 230 8
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExportThemisPaperServiceImpl.java
  65. 643 615
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExtractConfigProviderServiceImpl.java
  66. 1313 1293
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExtractConfigServiceImpl.java
  67. 2 2
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/GenPaperService.java
  68. 29 47
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/PaperDetailUnitServiceImpl.java
  69. 2396 2213
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/PaperServiceImpl.java
  70. 17 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/QuestionAudioServiceImpl.java
  71. 35 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/RandomPaperQuestionServiceImpl.java
  72. 1208 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/RandomPaperServiceImpl.java
  73. 3 3
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/temp/CqdxService.java
  74. 3 6
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/themispaper/ThemisBlock.java
  75. 52 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/util/BatchGetDataUtil.java
  76. 29 0
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/util/BatchSetDataUtil.java
  77. 107 1
      examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/util/PaperUtil.java
  78. 12 0
      examcloud-core-questions-starter/pom.xml
  79. 2 1
      examcloud-core-questions-starter/src/main/java/cn/com/qmth/examcloud/core/questions/starter/config/ExamCloudResourceManager.java
  80. 22 1
      jenkins.sh

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

@@ -1,46 +1,38 @@
 package cn.com.qmth.examcloud.core.questions.api.controller;
 
-import javax.servlet.http.HttpServletResponse;
-
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
-import cn.com.qmth.examcloud.api.commons.security.bean.User;
-import cn.com.qmth.examcloud.core.questions.service.CopyDataService;
-import cn.com.qmth.examcloud.web.redis.RedisClient;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
-import cn.com.qmth.examcloud.web.support.ServletUtil;
 
 @RestController
 @RequestMapping("${api_cqb}/copy")
 public class CopyDataController extends ControllerSupport {
-	private static int cacheLockTimeout = 60 * 10;
-	@Autowired
-	private CopyDataService copyDataService;
-	@Autowired
-	private RedisClient redisClient;
-
-	//西交大数据复制
-	@GetMapping("data")
-	public void copyData(HttpServletResponse response) {
-		Long fromRootOrgId = 371L;
-		Long toRootOrgId = 21595L;
-//		Long fromRootOrgId = 141L;
-//		Long toRootOrgId = 150L;
-		User user = getAccessUser();
-		if (!user.getRootOrgId().equals(toRootOrgId)) {
-			ServletUtil.returnJson("请求失败,没有权限", response);
-			return;
-		}
-		String cacheLock = "$_COPY_QUESTION_DATA_LOCK";
-		Boolean lock = redisClient.setIfAbsent(cacheLock, cacheLock, cacheLockTimeout);
-		if (!lock) {
-			ServletUtil.returnJson("请求失败,正在处理数据", response);
-			return;
-		}
-		copyDataService.copyData(getAccessUser(), fromRootOrgId, toRootOrgId);
-	}
+//	private static int cacheLockTimeout = 60 * 10;
+//	@Autowired
+//	private CopyDataService copyDataService;
+//	@Autowired
+//	private RedisClient redisClient;
+//
+//	//西交大数据复制
+//	@GetMapping("data")
+//	public void copyData(HttpServletResponse response) {
+//		Long fromRootOrgId = 371L;
+//		Long toRootOrgId = 21595L;
+////		Long fromRootOrgId = 141L;
+////		Long toRootOrgId = 150L;
+//		User user = getAccessUser();
+//		if (!user.getRootOrgId().equals(toRootOrgId)) {
+//			ServletUtil.returnJson("请求失败,没有权限", response);
+//			return;
+//		}
+//		String cacheLock = "$_COPY_QUESTION_DATA_LOCK";
+//		Boolean lock = redisClient.setIfAbsent(cacheLock, cacheLock, cacheLockTimeout);
+//		if (!lock) {
+//			ServletUtil.returnJson("请求失败,正在处理数据", response);
+//			return;
+//		}
+//		copyDataService.copyData(getAccessUser(), fromRootOrgId, toRootOrgId);
+//	}
 
 }

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

@@ -4,6 +4,7 @@ import cn.com.qmth.examcloud.commons.util.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.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.*;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
@@ -25,21 +26,30 @@ 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;
 
@@ -76,11 +86,11 @@ public class DemoController {
         log.info("--->试题答案缓存: " + new JsonMapper().toJson(questionAnswer));
         //questionAnswerCache.remove(questionId);
 
-        final String pKeys = Constants.CACHE_KEY_PAPER + "*" + paperId;
+        final String pKeys = CacheConstants.CACHE_Q_EXTRACT_CONFIG_PAPER + "*" + paperId;
         Set<String> paperKeys = redisTemplate.keys(pKeys);
         log.info(StringUtils.join(paperKeys, ","));
 
-        final String qKeys = Constants.CACHE_KEY_QUESTION + "*" + questionId;
+        final String qKeys = CacheConstants.CACHE_Q_QUESTION + "*" + questionId;
         Set<String> questionKeys = redisTemplate.keys(qKeys);
         log.info(StringUtils.join(questionKeys, ","));
 

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

@@ -1,6 +1,5 @@
 package cn.com.qmth.examcloud.core.questions.api.controller;
 
-
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -24,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestParam;
 
 import cn.com.qmth.examcloud.api.commons.enums.AdminOperateType;
+import cn.com.qmth.examcloud.api.commons.enums.CallType;
 import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
@@ -46,7 +46,6 @@ import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import cn.com.qmth.examcloud.web.support.Naked;
 import io.swagger.annotations.ApiOperation;
 
-
 /**
  * @author chenken
  * @date 2017年4月14日 下午6:05:37
@@ -57,304 +56,322 @@ import io.swagger.annotations.ApiOperation;
 @Controller
 @RequestMapping("${api_cqb}/")
 public class ExtractConfigController extends ControllerSupport {
-    private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigController.class);
+	private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigController.class);
+
+	@Autowired
+	private ExtractConfigService extractConfigService;
+
+	@Autowired
+	private ExtractConfigFileService extractConfigFileService;
+
+	@Autowired
+	private ExtractConfigCache extractConfigCache;
 
-    @Autowired
-    private ExtractConfigService extractConfigService;
+	@DataRule(type = DataRuleType.COURSE)
+	@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();
+			UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
+			Page<ExtractConfig> extractConfigPageList = extractConfigService.findPageExtractConfig(currentPage,
+					pageSize, examId, courseNo, String.valueOf(user.getRootOrgId()), ud);
+			return new ResponseEntity<>(extractConfigPageList, HttpStatus.OK);
+		} catch (Exception e) {
+			LOG.error(e.getMessage(), e);
+			throw new StatusException("Q-050065", e.getMessage());
+		}
+	}
 
-    @Autowired
-    private ExtractConfigFileService extractConfigFileService;
+	@ApiOperation(value = "保存调卷规则", notes = "保存调卷规则")
+	@PutMapping(value = "/extractConfig/{isBuildFile}")
+	public ResponseEntity<Object> saveExtractConfig(@PathVariable Integer isBuildFile,
+			@RequestBody ExtractConfig extractConfig, @RequestParam(required = false) PaperSeqMode seqMode) {
+		if (extractConfig.getCallType() == null) {
+			throw new StatusException("CallType不能为空");
+		}
+		String changeInfo = null;
+		if (extractConfig.getId() != null) {
+			changeInfo = getChangeInfo(extractConfig);
+		}
+		User user = getAccessUser();
+		extractConfig.setOrgId(user.getRootOrgId() + "");
+		extractConfig.setOrgName(user.getRootOrgName());
+		if (!CallType.RANDOM_PAPER.equals(extractConfig.getCallType())) {
+			extractConfigFileService.saveExtractConfigAndBuildPaperFile(extractConfig, isBuildFile, user, seqMode);
+		} else {
+			if (extractConfig.getRandomPaperId() == null) {
+				throw new StatusException("请选择随机抽题模板");
+			}
+			if (extractConfig.getPlayTime() == null) {
+				throw new StatusException("请填写音频播放次数");
+			}
+			extractConfigService.saveExtractConfigForRandomPaper(extractConfig);
+		}
 
-    @Autowired
-    private ExtractConfigCache extractConfigCache;
+		// 清除缓存
+		extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
+		if (extractConfig.getId() == null) {
+			ReportsUtil.report(
+					new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE23.getDesc(),
+							"课程ID:" + extractConfig.getCourse().getId() + " 考试ID:" + extractConfig.getExamId()));
+		} else {
+			if (changeInfo != null) {
+				ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+						AdminOperateType.TYPE24.getDesc(), "课程ID:" + extractConfig.getCourse().getId() + " 考试ID:"
+								+ extractConfig.getExamId() + changeInfo));
+			}
+		}
+		return new ResponseEntity<>(HttpStatus.OK);
+	}
 
-    @DataRule(type = DataRuleType.COURSE)
-    @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();
-            UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
-            Page<ExtractConfig> extractConfigPageList = extractConfigService.findPageExtractConfig(currentPage, pageSize, examId, courseNo, String.valueOf(user.getRootOrgId()),ud);
-            return new ResponseEntity<>(extractConfigPageList, HttpStatus.OK);
-        } catch (Exception e) {
-            LOG.error(e.getMessage(), e);
-            throw new StatusException("Q-050065", e.getMessage());
-        }
-    }
+	private String getChangeInfo(ExtractConfig now) {
+		ExtractConfig old = extractConfigService.findConfigById(now.getId());
+		StringBuilder sb = new StringBuilder();
+		if (!CallType.RANDOM_PAPER.equals(now.getCallType())) {
+			boolean paperChange = paperChange(old, now);
+			if (paperChange) {
+				sb.append("修改试卷选定,");
+			}
+			if (!paperChange && paperWeightChange(old, now)) {
+				sb.append("修改试卷选取几率,");
+			}
+			if (old.getScrambling_the_question_order() != now.getScrambling_the_question_order()) {
+				sb.append("修改客观题小题乱序,");
+			}
+			if (old.getScrambling_the_option_order() != now.getScrambling_the_option_order()) {
+				sb.append("修改客观题选项乱序,");
+			}
+			if (old.getIfFinish() != now.getIfFinish()) {
+				sb.append("修改是否生成文件,");
+			}
+		} else {
+			if (old.getRandomPaperId() != now.getRandomPaperId()) {
+				sb.append("修改随机抽题模板,");
+			}
+			if (old.getPlayTime() != now.getPlayTime()) {
+				sb.append("修改音频播放次数,");
+			}
+		}
+		if (sb.length() != 0) {
+			sb.deleteCharAt(sb.length() - 1);
+			return " 修改内容:" + sb.toString();
+		}
+		return null;
+	}
 
-    @ApiOperation(value = "保存调卷规则", notes = "保存调卷规则")
-    @PutMapping(value = "/extractConfig/{isBuildFile}")
-    public ResponseEntity<Object> saveExtractConfig(@PathVariable Integer isBuildFile, @RequestBody ExtractConfig extractConfig, @RequestParam(required = false) PaperSeqMode seqMode) {
-    	String changeInfo=null;
-    	if(extractConfig.getId()!=null) {
-    		changeInfo=getChangeInfo(extractConfig);
-    	}
-        User user = getAccessUser();
-        extractConfig.setOrgId(user.getRootOrgId() + "");
-        extractConfig.setOrgName(user.getRootOrgName());
-        extractConfigFileService.saveExtractConfigAndBuildPaperFile(extractConfig, isBuildFile, user,seqMode);
+	private boolean paperChange(ExtractConfig old, ExtractConfig now) {
+		if (old.getExamPaperList().size() != now.getExamPaperList().size()) {
+			return true;
+		}
+		for (int i = 0; i < old.getExamPaperList().size(); i++) {
+			ExamPaper oldPaper = old.getExamPaperList().get(i);
+			ExamPaper nowPaper = now.getExamPaperList().get(i);
+			if (!oldPaper.getPaper().getId().equals(nowPaper.getPaper().getId())) {
+				return true;
+			}
+		}
+		return false;
+	}
 
-        //清除缓存
-        extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
-        if(extractConfig.getId()==null) {
-        	ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE23.getDesc(),"课程ID:"+extractConfig.getCourse().getId()+" 考试ID:"+extractConfig.getExamId()));
-        }else {
-        	if(changeInfo!=null) {
-        		ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE24.getDesc(),"课程ID:"+extractConfig.getCourse().getId()+" 考试ID:"+extractConfig.getExamId()+changeInfo));
-        	}
-        }
-        return new ResponseEntity<>(HttpStatus.OK);
-    }
-    
-    private String getChangeInfo(ExtractConfig now) {
-    	ExtractConfig old=extractConfigService.findConfigById(now.getId());
-    	StringBuilder sb=new StringBuilder();
-    	boolean paperChange=paperChange(old, now);
-    	if(paperChange) {
-    		sb.append("修改试卷选定,");
-    	}
-    	if(!paperChange&&paperWeightChange(old, now)) {
-    		sb.append("修改试卷选取几率,");
-    	}
-    	if(old.getScrambling_the_question_order()!=now.getScrambling_the_question_order()) {
-    		sb.append("修改客观题小题乱序,");
-    	}
-    	if(old.getScrambling_the_option_order()!=now.getScrambling_the_option_order()) {
-    		sb.append("修改客观题选项乱序,");
-    	}
-    	if(old.getIfFinish()!=now.getIfFinish()) {
-    		sb.append("修改是否生成文件,");
-    	}
-    	if(sb.length()!=0) {
-    		sb.deleteCharAt(sb.length()-1);
-    		return " 修改内容:"+sb.toString();
-    	}
-    	return null;
-    }
-    
-    private boolean paperChange(ExtractConfig old,ExtractConfig now) {
-    	if(old.getExamPaperList().size()!=now.getExamPaperList().size()) {
-    		return true;
-    	}
-    	for(int i=0;i<old.getExamPaperList().size();i++) {
-    		ExamPaper oldPaper=old.getExamPaperList().get(i);
-    		ExamPaper nowPaper=now.getExamPaperList().get(i);
-    		if(!oldPaper.getPaper().getId().equals(nowPaper.getPaper().getId())) {
-    			return true;
-    		}
-    	}
-    	return false;
-    }
-    
-    private boolean paperWeightChange(ExtractConfig old,ExtractConfig now) {
-    	for(int i=0;i<old.getExamPaperList().size();i++) {
-    		ExamPaper oldPaper=old.getExamPaperList().get(i);
-    		ExamPaper nowPaper=now.getExamPaperList().get(i);
-    		if(!oldPaper.getWeight().equals(nowPaper.getWeight())) {
-    			return true;
-    		}
-    	}
-    	return false;
-    }
+	private boolean paperWeightChange(ExtractConfig old, ExtractConfig now) {
+		for (int i = 0; i < old.getExamPaperList().size(); i++) {
+			ExamPaper oldPaper = old.getExamPaperList().get(i);
+			ExamPaper nowPaper = now.getExamPaperList().get(i);
+			if (!oldPaper.getWeight().equals(nowPaper.getWeight())) {
+				return true;
+			}
+		}
+		return false;
+	}
 
-    @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 = "/extractConfig/{id}")
+	public ResponseEntity<ExtractConfig> findExtractConfigById(@PathVariable String id) {
+		ExtractConfig extractConfig = extractConfigService.findConfigById(id);
+		return new ResponseEntity<ExtractConfig>(extractConfig, HttpStatus.OK);
+	}
 
-    @DataRule(type = DataRuleType.COURSE)
-    @ApiOperation(value = "根据考试Id获取已经制定的调卷规则", notes = "根据考试Id获取已经制定的调卷规则")
-    @GetMapping(value = "/findCourseByExtractConfig/{examId}")
-    public ResponseEntity<Object> findCourseByExtractConfig(@PathVariable Long examId) {
-        User user = getAccessUser();
-        UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
-        List<CouresInfo> courseCodeList = extractConfigService.findCourseByExtractConfig(examId, user.getRootOrgId().toString(),ud);
-        return new ResponseEntity<>(courseCodeList, HttpStatus.OK);
-    }
+	@DataRule(type = DataRuleType.COURSE)
+	@ApiOperation(value = "根据考试Id获取已经制定的调卷规则", notes = "根据考试Id获取已经制定的调卷规则")
+	@GetMapping(value = "/findCourseByExtractConfig/{examId}")
+	public ResponseEntity<Object> findCourseByExtractConfig(@PathVariable Long examId) {
+		User user = getAccessUser();
+		UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
+		List<CouresInfo> courseCodeList = extractConfigService.findCourseByExtractConfig(examId,
+				user.getRootOrgId().toString(), ud);
+		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);
-    }
+	@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) {
-            LOG.error("抽卷失败", e);
-            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
-        }
-    }
+	@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) {
+			LOG.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) {
-            LOG.error("抽题失败", e);
-            return new ResponseEntity<>("抽题失败:" + e.getMessage(), 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) {
+			LOG.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) {
-            LOG.error("调用checkIsAllQbjectiveQuestion失败", e);
-            quesMap.put("result", "error");
-            quesMap.put("message", "调用失败");
-            return new ResponseEntity<>(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) {
+			LOG.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);
-        }
-    }
+	@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());
-        }
-    }
+	@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 (StatusException e) {
-            throw e;
-        }	catch (Exception e) {
-            throw new StatusException("500","系统错误",e);
-        }
-    }
+	@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 (StatusException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new StatusException("500", "系统错误", 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) {
-            LOG.error("抽卷失败", e);
-            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
-        }
-    }
+	@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) {
+			LOG.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) {
-            LOG.error("获取试卷Dto失败", 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) {
+			LOG.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);
-    }
+	@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);
+	}
 
-}	
+}

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

@@ -1,5 +1,46 @@
 package cn.com.qmth.examcloud.core.questions.api.controller;
 
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+
+import org.apache.commons.collections4.CollectionUtils;
+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.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.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.multipart.MultipartFile;
+
+import com.google.gson.Gson;
+
 import cn.com.qmth.examcloud.api.commons.enums.AdminOperateType;
 import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
@@ -37,33 +78,9 @@ import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import cn.com.qmth.examcloud.web.support.Naked;
-import com.google.gson.Gson;
 import freemarker.template.TemplateException;
 import io.swagger.annotations.ApiOperation;
 
-import org.apache.commons.collections4.CollectionUtils;
-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 javax.servlet.http.HttpServletResponse;
-import javax.validation.constraints.NotNull;
-import java.io.File;
-import java.io.IOException;
-import java.io.StringWriter;
-import java.math.BigDecimal;
-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
@@ -257,7 +274,8 @@ public class PaperController extends ControllerSupport {
     @ApiOperation(value = "查询所有导入试卷(not in paperIds)", notes = "查询所有导入试卷(not in paperIds)")
     @GetMapping(value = "/genPaper/huoge/{curPage}/{pageSize}")
     public ResponseEntity<Object> getGenPapersNotInIds(@ModelAttribute PaperSearchInfo paperSearchInfo,
-                                                       @RequestParam String ids, @PathVariable int curPage, @PathVariable int pageSize) {
+                                                       @RequestParam String ids, @PathVariable int curPage, 
+                                                       @PathVariable int pageSize) {
     	User user = getAccessUser();
     	UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
         paperSearchInfo.setOrgId(user.getRootOrgId().toString());
@@ -274,7 +292,8 @@ public class PaperController extends ControllerSupport {
                 ret = paperService.getGenPapers(paperSearchInfo, curPage - 1, pageSize,ud);
             }
         }
-        if(ret!=null&&CollectionUtils.isNotEmpty(ret.getContent())) {
+        if(ret!=null&&CollectionUtils.isNotEmpty(ret.getContent())
+        		&&paperSearchInfo.getFillCount()!=null&&paperSearchInfo.getFillCount()) {
         	setPaperQuesCountAndScore(ret.getContent());
         }
         return new ResponseEntity<>(ret, HttpStatus.OK);
@@ -288,11 +307,13 @@ public class PaperController extends ControllerSupport {
      */
     @ResponseBody
     @ApiOperation(value = "删除试卷", notes = "删除试卷")
-    @DeleteMapping(value = "/paper/{paperIds}")
-    public void delPaper(@PathVariable String paperIds) {
+    @PostMapping(value = "/paper")
+    public void delPaper(@RequestParam List<String> paperIds) {
         User user = getAccessUser();
-        List<String> paperList = Stream.of(paperIds.split(",")).collect(Collectors.toList());
-        paperService.deletePapers(paperList, user);
+        if(CollectionUtils.isEmpty(paperIds)) {
+        	throw new StatusException("请选择试卷");
+        }
+        paperService.deletePapersPlus(paperIds, user);
     }
 
     /**
@@ -430,10 +451,9 @@ public class PaperController extends ControllerSupport {
     @ResponseBody
     @ApiOperation(value = "删除试卷中的试题", notes = "删除试卷中的试题")
     @DeleteMapping(value = "/paper/deleteQuestion/{questionId}")
-    public ResponseEntity<Object> deletePaperQuestion(@PathVariable String questionId) {
+    public void deletePaperQuestion(@PathVariable String questionId) {
         User user = getAccessUser();
-        List<String> paperNames = paperService.deleteImportQuestionById(null, questionId, user);
-        return new ResponseEntity<>(paperNames, HttpStatus.OK);
+        paperService.deleteImportQuestionById(null, questionId, user);
     }
 
     /**
@@ -445,10 +465,9 @@ public class PaperController extends ControllerSupport {
     @ResponseBody
     @ApiOperation(value = "删除导入试卷中的试题", notes = "删除导入试卷中的试题")
     @DeleteMapping(value = "/paper/deleteQuestion/{detailUnitId}/{questionId}")
-    public ResponseEntity<Object> deleteImportPaperQuestion(@PathVariable String detailUnitId, @PathVariable String questionId) {
+    public void deleteImportPaperQuestion(@PathVariable String detailUnitId, @PathVariable String questionId) {
         User user = getAccessUser();
-        List<String> paperNames = paperService.deleteImportQuestionById(detailUnitId, questionId, user);
-        return new ResponseEntity<>(paperNames, HttpStatus.OK);
+        paperService.deleteImportQuestionById(detailUnitId, questionId, user);
     }
 
     /**
@@ -623,7 +642,8 @@ public class PaperController extends ControllerSupport {
                 ret = paperService.getImportPapers(paperSearchInfo, curPage - 1, pageSize,ud);
             }
         }
-        if(ret!=null&&CollectionUtils.isNotEmpty(ret.getContent())) {
+        if(ret!=null&&CollectionUtils.isNotEmpty(ret.getContent())
+        		&&paperSearchInfo.getFillCount()!=null&&paperSearchInfo.getFillCount()) {
         	setPaperQuesCountAndScore(ret.getContent());
         }
         return new ResponseEntity<>(ret, HttpStatus.OK);
@@ -637,7 +657,7 @@ public class PaperController extends ControllerSupport {
             initQuesCountAndScore(paper);
             Map<String, Integer> quesCount = paper.getQuesCount();
             Map<String, Double> quesScore = paper.getQuesScore();
-            List<PaperDetailUnit> units = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+            List<PaperDetailUnit> units = paperDetailUnitRepo.findByPaper(paper);
             if (units != null && units.size() > 0) {
                 for (PaperDetailUnit unit : units) {
                     if (unit.getQuestion().getPublicity() == null) {

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

@@ -9,6 +9,7 @@ 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.RandomPaperService;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.QuesNameDto;
 import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
@@ -32,10 +33,13 @@ import java.util.stream.Stream;
 public class PaperStructController extends ControllerSupport {
 
     @Autowired
-    PaperStructService paperStructService;
+    private PaperStructService paperStructService;
+    
+    @Autowired
+    private RandomPaperService randomPaperService;
 
     @Autowired
-    PaperStructRepo paperStructRepo;
+    private PaperStructRepo paperStructRepo;
 
     /**
      * 获取所有试卷结构
@@ -100,6 +104,9 @@ public class PaperStructController extends ControllerSupport {
     public ResponseEntity<Object> updatePaperStruct(@RequestBody PaperStruct ps) {
         User user = getAccessUser();
         ps.setOrgId(user.getRootOrgId()+"");
+        if(randomPaperService.existStruct(ps.getId())) {
+        	throw new StatusException("该组卷结构已被抽题模板使用,不能修改");
+        }
         PaperStruct paperStruct = paperStructService.save(ps, user);
         if (paperStruct == null) {
             return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
@@ -138,7 +145,7 @@ public class PaperStructController extends ControllerSupport {
     @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));
+        paperStructService.removePaperStruct(paperList);
         return new ResponseEntity<>(HttpStatus.OK);
     }
 

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

@@ -7,7 +7,6 @@ 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;
@@ -24,11 +23,14 @@ import cn.com.qmth.examcloud.api.commons.enums.DataRuleType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.base.Model;
 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.core.questions.service.RandomPaperQuestionService;
+import cn.com.qmth.examcloud.core.questions.service.util.PaperUtil;
 import cn.com.qmth.examcloud.web.security.DataRule;
 import cn.com.qmth.examcloud.web.support.ControllerSupport;
 import io.swagger.annotations.ApiOperation;
@@ -40,147 +42,153 @@ import io.swagger.annotations.ApiOperation;
 @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
-     */
-    @DataRule(type = DataRuleType.COURSE)
-    @ApiOperation(value = "分页查询试题", notes = "分页查询试题")
-    @GetMapping(value = "/question/{curPage}/{pageSize}")
-    public ResponseEntity<Object> getAllQuestion(@ModelAttribute QuestionSearchCondition searchCondition,
-                                                 @PathVariable int curPage,
-                                                 @PathVariable int pageSize) {
-        User user = getAccessUser();
-        UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
-        searchCondition.setOrgId(user.getRootOrgId().toString());
-        return new ResponseEntity<>(quesService.findAll(searchCondition, curPage, pageSize,ud), 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) {
-    	ResponseEntity<Object> ret=new ResponseEntity<>(quesService.saveQues(question), HttpStatus.CREATED);
-        return  ret;
-    }
-
-    /**
-     * 删除试题
-     *
-     * @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", "难度和公开度不能都为空");
+	@Autowired
+	QuesService quesService;
+
+	@Autowired
+	QuesRepo quesRepo;
+
+	@Autowired
+	Gson gson;
+
+	@Autowired
+	private PaperDetailUnitService paperDetailUnitService;
+	
+	@Autowired
+	private RandomPaperQuestionService randomPaperQuestionService;
+	
+	/**
+	 * 获取试题
+	 *
+	 * @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
+	 */
+	@DataRule(type = DataRuleType.COURSE)
+	@ApiOperation(value = "分页查询试题", notes = "分页查询试题")
+	@GetMapping(value = "/question/{curPage}/{pageSize}")
+	public ResponseEntity<Object> getAllQuestion(@ModelAttribute QuestionSearchCondition searchCondition,
+			@PathVariable int curPage, @PathVariable int pageSize) {
+		User user = getAccessUser();
+		UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
+		searchCondition.setOrgId(user.getRootOrgId().toString());
+		return new ResponseEntity<>(quesService.findAll(searchCondition, curPage, pageSize, ud), HttpStatus.OK);
+	}
+
+	/**
+	 * 更新试题信息
+	 *
+	 * @param
+	 * @return
+	 */
+	@ApiOperation(value = "更新试题", notes = "更新试题")
+	@PutMapping(value = "/question")
+	public ResponseEntity<Object> updateQuestion(@RequestBody Question question) {
+		Question old = Model.of(quesRepo.findById(question.getId()));
+		if(paperDetailUnitService.paperInUse(question.getId())) {
+        	PaperUtil.checkUpdate(question, old,"试卷已调用,");
         }
-        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);
-    }
-
+        if(randomPaperQuestionService.existQuestion(question.getId())) {
+        	PaperUtil.checkUpdateOption(question, old,"小题已被抽题模板使用,");
+        }
+		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) {
+		ResponseEntity<Object> ret = new ResponseEntity<>(quesService.saveQues(question), HttpStatus.CREATED);
+		return ret;
+	}
+
+	/**
+	 * 删除试题
+	 *
+	 * @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);
+	}
 
 }

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

@@ -0,0 +1,98 @@
+package cn.com.qmth.examcloud.core.questions.api.controller;
+
+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.PostMapping;
+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.api.commons.enums.DataRuleType;
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.api.request.GetRandomPaperReq;
+import cn.com.qmth.examcloud.core.questions.api.response.GetRandomPaperResp;
+import cn.com.qmth.examcloud.core.questions.service.RandomPaperService;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.PaperQuestionViewQuery;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperDomain;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperListVo;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperQuery;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.StructInfo;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
+import cn.com.qmth.examcloud.web.security.DataRule;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+
+@RestController
+@Api(tags = "随机组卷模板")
+@RequestMapping("${api_cqb}/randompaper")
+public class RandomPaperController extends ControllerSupport {
+
+	@Autowired
+	private RandomPaperService randomPaperService;
+
+	@DataRule(type = DataRuleType.COURSE)
+	@ApiOperation("获取分页")
+	@PostMapping("page")
+	public Page<RandomPaperListVo> getPage(RandomPaperQuery req) {
+		User user = getAccessUser();
+		UserDataRule ud = super.getUserDataRule(DataRuleType.COURSE);
+		req.setUd(ud);
+		req.setRootOrgId(user.getRootOrgId());
+		return randomPaperService.getPage(req);
+	}
+	
+	@ApiOperation("获取信息")
+	@PostMapping("info")
+	public RandomPaperListVo getInfo(@RequestParam String id) {
+		return randomPaperService.getInfo(id);
+	}
+
+	@ApiOperation(value = "启用、禁用")
+	@PostMapping("toggle")
+	public void toggle(@RequestParam String id, @RequestParam Boolean enable) {
+		randomPaperService.toggle(id, enable, getAccessUser());
+	}
+
+	@ApiOperation(value = "获取组卷结构试题信息")
+	@PostMapping("struct/question/info")
+	public StructInfo getStructQuestionInfo(@RequestParam String structId) {
+		if(StringUtils.isBlank(structId)) {
+			throw new StatusException("组卷结构ID不能为空");
+		}
+		return randomPaperService.getStructQuestionInfo(structId);
+	}
+
+	@ApiOperation(value = "选中试卷范围预览")
+	@PostMapping("struct/question/view/info")
+	public StructInfo getPaperQuestionViewInfo(PaperQuestionViewQuery query) {
+		return randomPaperService.getPaperQuestionViewInfo(query);
+	}
+	
+	@ApiOperation(value = "保存模板")
+	@PostMapping("save")
+	public StructInfo save(RandomPaperDomain domain) {
+		if (StringUtils.isBlank(domain.getId())) {
+			domain.setRootOrgId(getAccessUser().getRootOrgId());
+		}
+		return randomPaperService.saveRandomPaper(domain);
+	}
+	
+	@ApiOperation(value = "获取随机试卷")
+	@PostMapping("create")
+	public GetRandomPaperResp getRandomPaper(GetRandomPaperReq req) {
+    	if (StringUtils.isBlank(req.getRandomPaperId())) {
+            throw new StatusException("RandomPaperId is null");
+        }
+    	if (req.getPlayTime()==null) {
+            throw new StatusException("PlayTime is null");
+        }
+    	GetRandomPaperResp res=new GetRandomPaperResp();
+    	DefaultPaper defaultPaper=randomPaperService.getRandomPaper(req.getRandomPaperId(),req.getPlayTime());
+    	res.setDefaultPaper(defaultPaper);
+		return res;
+	}
+}

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

@@ -5,6 +5,7 @@ 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.RandomPaperService;
 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;
@@ -41,6 +42,9 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
 
     @Autowired
     private ExtractConfigProviderService extractConfigExamService;
+    
+    @Autowired
+    private RandomPaperService randomPaperService;
 
     @ApiOperation(value = "根据调卷规则抽取考试试卷")
     @PostMapping("getPaper")
@@ -169,5 +173,21 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
         }
         return null;
     }
+    
+    @ApiOperation("随机抽卷")
+    @PostMapping("randompaper")
+    @Override
+	public GetRandomPaperResp getRandomPaper(@RequestBody GetRandomPaperReq req) {
+    	if (StringUtils.isBlank(req.getRandomPaperId())) {
+            throw new StatusException("RandomPaperId is null");
+        }
+    	if (req.getPlayTime()==null) {
+            throw new StatusException("PlayTime is null");
+        }
+    	GetRandomPaperResp res=new GetRandomPaperResp();
+    	DefaultPaper defaultPaper=randomPaperService.getRandomPaper(req.getRandomPaperId(),req.getPlayTime());
+    	res.setDefaultPaper(defaultPaper);
+		return res;
+	}
 
 }

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

@@ -1,5 +1,13 @@
 package cn.com.qmth.examcloud.core.questions.api.provider;
 
+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 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;
@@ -8,13 +16,6 @@ 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
@@ -32,7 +33,7 @@ public class PaperCloudServiceProvider implements PaperCloudService {
     private static final long serialVersionUID = -7354673848303599874L;
 
     @Autowired
-    PaperProviderService paperProviderService;
+    private PaperProviderService paperProviderService;
 
     @ApiOperation(value = "外部接口组卷", notes = "外部接口组卷")
     @PostMapping("/genPaper")
@@ -70,4 +71,5 @@ public class PaperCloudServiceProvider implements PaperCloudService {
         return resp;
     }
 
+
 }

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

@@ -1,26 +1,10 @@
 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
@@ -36,60 +20,4 @@ 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;
-    }
-
 }

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

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.core.questions.api.provider;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+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 cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.questions.api.RandomPaperCloudService;
+import cn.com.qmth.examcloud.core.questions.api.bean.RandomPaperBean;
+import cn.com.qmth.examcloud.core.questions.api.request.GetRandomPaperBeanReq;
+import cn.com.qmth.examcloud.core.questions.api.response.GetRandomPaperBeanResp;
+import cn.com.qmth.examcloud.core.questions.dao.RandomPaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.RandomPaper;
+import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
+import io.swagger.annotations.ApiOperation;
+
+
+@RestController
+@RequestMapping("${$rmp.cloud.questions}" + "randompaper")
+public class RandomPaperCloudServiceProvider implements RandomPaperCloudService {
+
+    private static final long serialVersionUID = -7354673848303599874L;
+
+    @Autowired
+    private RandomPaperRepo randomPaperRepo;
+
+
+    @ApiOperation(value = "获取信息")
+    @PostMapping("info")
+    @Override
+	public GetRandomPaperBeanResp getRandomPaperBean(@RequestBody GetRandomPaperBeanReq req) {
+    	if (StringUtils.isBlank(req.getRandomPaperId())) {
+            throw new StatusException("RandomPaperId is null");
+        }
+    	RandomPaper p = GlobalHelper.getEntity(randomPaperRepo, req.getRandomPaperId(), RandomPaper.class);
+    	if(p==null) {
+    		throw new StatusException("未找到抽卷模板信息");
+    	}
+    	GetRandomPaperBeanResp res=new GetRandomPaperBeanResp();
+    	RandomPaperBean bean=new RandomPaperBean();
+    	bean.setId(p.getId());
+    	bean.setName(p.getName());
+    	res.setRandomPaper(bean);
+    	
+		return res;
+	}
+
+
+}

+ 14 - 31
examcloud-core-questions-base/pom.xml

@@ -12,32 +12,11 @@
     </parent>
 
     <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>${project.version}</version>
-        </dependency>
         <dependency>
             <groupId>cn.com.qmth.examcloud</groupId>
             <artifactId>examcloud-support</artifactId>
             <version>${project.version}</version>
         </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-question-commons</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-
         <dependency>
             <groupId>cn.com.qmth.examcloud.rpc</groupId>
             <artifactId>examcloud-global-api-client</artifactId>
@@ -58,16 +37,6 @@
             <artifactId>examcloud-core-print-api-client</artifactId>
             <version>${project.version}</version>
         </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud.rpc</groupId>
-            <artifactId>examcloud-core-questions-api</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>cn.com.qmth.examcloud</groupId>
-            <artifactId>examcloud-reports-commons</artifactId>
-            <version>${project.version}</version>
-        </dependency>
         <dependency>
             <groupId>cn.com.qmth.examcloud.rpc</groupId>
             <artifactId>examcloud-core-oe-admin-api-client</artifactId>
@@ -78,6 +47,16 @@
             <artifactId>examcloud-core-oe-student-api-client</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <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>com.upyun</groupId>
             <artifactId>java-sdk</artifactId>
@@ -108,6 +87,10 @@
                     <groupId>org.apache.xmlgraphics</groupId>
                     <artifactId>xmlgraphics-commons</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>xml-apis</groupId>
+                    <artifactId>xml-apis</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>

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

@@ -0,0 +1,45 @@
+/*
+ * *************************************************
+ * Copyright (c) 2019 QMTH. All Rights Reserved. Created by Deason on 2019-09-24
+ * 14:10:50. *************************************************
+ */
+
+package cn.com.qmth.examcloud.core.questions.base.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class PagerQuery {
+
+    @ApiModelProperty(value = "第几页(从1开始)")
+    protected Integer pageNumber;
+
+    @ApiModelProperty(value = "每页条数")
+    protected Integer pageSize;
+
+    public Integer getPageNumber() {
+        // 默认值
+        if (pageNumber == null || pageNumber < 1) {
+        	pageNumber = 1;
+        }
+
+        return pageNumber;
+    }
+
+    public Integer getPageSize() {
+        // 默认值
+        if (pageSize == null || pageSize < 1) {
+            pageSize = 10;
+        }
+
+        return pageSize;
+    }
+
+    public void setPageNumber(Integer pageNumber) {
+        this.pageNumber = pageNumber;
+    }
+
+    public void setPageSize(Integer pageSize) {
+        this.pageSize = pageSize;
+    }
+
+}

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

@@ -37,8 +37,5 @@ public enum PaperType {
         return name;
     }
 
-    public String toString() {
-        return getName();
-    }
 
 }

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

@@ -0,0 +1,16 @@
+package cn.com.qmth.examcloud.core.questions.base.enums;
+
+public enum QuestionDifficulty {
+	HARD("难"), MEDIUM("中"), EASY("易");
+
+	private final String name;
+
+	QuestionDifficulty(String name) {
+		this.name = name;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+}

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

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.questions.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.mongodb.MongoDatabaseFactory;
+import org.springframework.data.mongodb.core.MongoTemplate;
+import org.springframework.data.mongodb.core.convert.DbRefResolver;
+import org.springframework.data.mongodb.core.convert.DefaultDbRefResolver;
+import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
+import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
+
+@Configuration
+public class MongoConfig {
+
+    private final MongoDatabaseFactory mongoDbFactory;
+
+    private final MongoMappingContext mongoMappingContext;
+
+    public MongoConfig(MongoDatabaseFactory mongoDbFactory, MongoMappingContext mongoMappingContext) {
+        this.mongoDbFactory = mongoDbFactory;
+        this.mongoMappingContext = mongoMappingContext;
+    }
+
+    @Primary
+    @Bean(name = "mongoTemplate")
+    public MongoTemplate mongoTemplate() {
+        // 支持懒加载
+        DbRefResolver dbRefResolver = new DefaultDbRefResolver(mongoDbFactory);
+        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
+        // converter.setTypeMapper(new DefaultMongoTypeMapper(null));
+        return new MongoTemplate(mongoDbFactory, converter);
+    }
+
+    @Bean(name = "mongoTemplate2")
+    public MongoTemplate mongoTemplate2() {
+        // 不支持懒加载
+        DbRefResolver dbRefResolver = MyDbRefResolver.INSTANCE;
+        MappingMongoConverter converter = new MappingMongoConverter(dbRefResolver, mongoMappingContext);
+        // converter.setTypeMapper(new DefaultMongoTypeMapper(null));
+        return new MongoTemplate(mongoDbFactory, converter);
+    }
+
+}

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

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.questions.config;
+
+import com.mongodb.DBRef;
+import org.bson.Document;
+import org.springframework.data.mongodb.core.convert.DbRefProxyHandler;
+import org.springframework.data.mongodb.core.convert.DbRefResolver;
+import org.springframework.data.mongodb.core.convert.DbRefResolverCallback;
+import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
+import org.springframework.lang.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum MyDbRefResolver implements DbRefResolver {
+
+    INSTANCE;
+
+    @Override
+    @Nullable
+    public Object resolveDbRef(MongoPersistentProperty property, @Nullable DBRef dbref,
+                               DbRefResolverCallback callback, DbRefProxyHandler proxyHandler) {
+        return callback.resolve(property);
+    }
+
+    @Override
+    @Nullable
+    public Document fetch(DBRef dbRef) {
+        Document document = new Document();
+        document.put("_id", dbRef.getId());
+        return document;
+    }
+
+    @Override
+    public List<Document> bulkFetch(List<DBRef> dbRefs) {
+        List<Document> list = new ArrayList<>();
+        for (DBRef dbRef : dbRefs) {
+            Document document = new Document();
+            document.put("_id", dbRef.getId());
+            list.add(document);
+        }
+        return list;
+    }
+
+}

+ 17 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/RandomPaperQuestionRepo.java

@@ -0,0 +1,17 @@
+package cn.com.qmth.examcloud.core.questions.dao;
+
+import java.util.List;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.RandomPaperQuestion;
+
+public interface RandomPaperQuestionRepo
+		extends MongoRepository<RandomPaperQuestion, String>, QueryByExampleExecutor<RandomPaperQuestion> {
+
+	void deleteByRandomPaperId(String id);
+
+	List<RandomPaperQuestion> findByRandomPaperId(String id);
+
+}

+ 13 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/RandomPaperRepo.java

@@ -0,0 +1,13 @@
+package cn.com.qmth.examcloud.core.questions.dao;
+
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.repository.query.QueryByExampleExecutor;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.RandomPaper;
+
+public interface RandomPaperRepo
+		extends MongoRepository<RandomPaper, String>, QueryByExampleExecutor<RandomPaper> {
+
+	RandomPaper findByRootOrgIdAndName(Long rootOrgId, String name);
+
+}

+ 247 - 238
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/ExtractConfig.java

@@ -4,6 +4,7 @@ import java.util.List;
 import java.util.Map;
 
 import cn.com.qmth.examcloud.core.questions.base.core.ExamCourseDto;
+import cn.com.qmth.examcloud.api.commons.enums.CallType;
 import cn.com.qmth.examcloud.core.questions.base.enums.ExtractPolicy;
 import cn.com.qmth.examcloud.core.questions.dao.entity.base.MongoBaseEntity;
 
@@ -13,246 +14,254 @@ import cn.com.qmth.examcloud.core.questions.dao.entity.base.MongoBaseEntity;
  * @author chenken
  */
 public class ExtractConfig extends MongoBaseEntity {
-    /**
+	/**
 	 * 
 	 */
 	private static final long serialVersionUID = -2723715817328472562L;
 	/**
-     * 考试ID
-     */
-    private Long examId;
-    /**
-     * 考试类型
-     * 网络考试
-     * 传统考试
-     */
-    private String examType;
-    /**
-     * 考试名称
-     */
-    private String examName;
-    /**
-     * 课程名称
-     */
-    private String courseName;
-    /**
-     * 课程代码
-     */
-    private String courseCode;
-
-    private Course course;
-    /**
-     * 调卷类型
-     * 成套调用
-     * 重组调用
-     */
-    private String callType;
-    /**
-     * 是否生成 1:生成 0:没生成
-     * 生成预览卷
-     */
-    private Short ifFinish;
-    /**
-     * 已经生成的试卷ID Map
-     * 数据结构:
-     * {
-     * "A":"0001",
-     * "B":"0002"
-     * }
-     * A 类型下生成的paperId为 0001
-     * B 类型下生成的paperId为 0002
-     */
-    private Map<String, String> finishedPaperIdMap;
-    /**
-     * 抽取试卷对象集合
-     */
-    private List<ExamPaper> examPaperList;
-    /**
-     * 小题乱序
-     * 1:乱序
-     * 0:不乱序
-     */
-    private Short scrambling_the_question_order;
-    /**
-     * 选项乱序
-     * 1:乱序
-     * 0:不乱序
-     */
-    private Short scrambling_the_option_order;
-
-    /**
-     * 抽卷规则
-     */
-    private ExtractPolicy policy;
-    /**
-     * 类型参数
-     */
-    private Map<String, Object> params;
-    /**
-     * 机构ID
-     */
-    private String orgId;
-
-    private String orgName;
-
-    public ExtractConfig() {
-
-    }
-
-    public ExtractConfig(String id) {
-        this.id = id;
-    }
-
-    public ExtractConfig(Long exam_id, String course_code) {
-        this.examId = exam_id;
-        this.courseCode = course_code;
-    }
-
-    public ExtractConfig(ExamCourseDto examCourseDto) {
-        this.examId = examCourseDto.getExamId();
-        this.examName = examCourseDto.getExamName();
-        this.courseCode = examCourseDto.getCourseCode();
-        this.courseName = examCourseDto.getCourseName();
-        this.examType = examCourseDto.getExamType().name();
-    }
-
-    public Long getExamId() {
-        return examId;
-    }
-
-    public void setExamId(Long examId) {
-        this.examId = examId;
-    }
-
-    public String getCourseCode() {
-        return courseCode;
-    }
-
-    public void setCourseCode(String courseCode) {
-        this.courseCode = courseCode;
-    }
-
-    public ExtractPolicy getPolicy() {
-        return policy;
-    }
-
-    public void setPolicy(ExtractPolicy policy) {
-        this.policy = policy;
-    }
-
-    public String getExamType() {
-        return examType;
-    }
-
-    public void setExamType(String examType) {
-        this.examType = examType;
-    }
-
-    public String getExamName() {
-        return examName;
-    }
-
-    public void setExamName(String examName) {
-        this.examName = examName;
-    }
-
-    public String getCourseName() {
-        return courseName;
-    }
-
-    public void setCourseName(String courseName) {
-        this.courseName = courseName;
-    }
-
-    public String getCallType() {
-        return callType;
-    }
-
-    public void setCallType(String callType) {
-        this.callType = callType;
-    }
-
-    public Short getIfFinish() {
-        return ifFinish;
-    }
-
-    public void setIfFinish(Short ifFinish) {
-        if (ifFinish == null) {
-            this.ifFinish = 0;
-        } else {
-            this.ifFinish = ifFinish;
-        }
-    }
-
-    public Map<String, Object> getParams() {
-        return params;
-    }
-
-    public void setParams(Map<String, Object> params) {
-        this.params = params;
-    }
-
-    public String getOrgId() {
-        return orgId;
-    }
-
-    public void setOrgId(String orgId) {
-        this.orgId = orgId;
-    }
-
-    public List<ExamPaper> getExamPaperList() {
-        return examPaperList;
-    }
-
-    public void setExamPaperList(List<ExamPaper> examPaperList) {
-        this.examPaperList = examPaperList;
-    }
-
-    public Short getScrambling_the_question_order() {
-        return scrambling_the_question_order;
-    }
-
-    public void setScrambling_the_question_order(Short scrambling_the_question_order) {
-        if (scrambling_the_question_order == null) {
-            this.scrambling_the_question_order = 0;
-        } else {
-            this.scrambling_the_question_order = scrambling_the_question_order;
-        }
-    }
-
-    public Short getScrambling_the_option_order() {
-        return scrambling_the_option_order;
-    }
-
-    public void setScrambling_the_option_order(Short scrambling_the_option_order) {
-        if (scrambling_the_option_order == null) {
-            this.scrambling_the_option_order = 0;
-        } else {
-            this.scrambling_the_option_order = scrambling_the_option_order;
-        }
-    }
-
-    public Map<String, String> getFinishedPaperIdMap() {
-        return finishedPaperIdMap;
-    }
-
-    public void setFinishedPaperIdMap(Map<String, String> finishedPaperIdMap) {
-        this.finishedPaperIdMap = finishedPaperIdMap;
-    }
-
-    public String getOrgName() {
-        return orgName;
-    }
-
-    public void setOrgName(String orgName) {
-        this.orgName = orgName;
-    }
-
-    public Course getCourse() {
-        return course;
-    }
-
-    public void setCourse(Course course) {
-        this.course = course;
-    }
+	 * 考试ID
+	 */
+	private Long examId;
+	/**
+	 * 考试类型 网络考试 传统考试
+	 */
+	private String examType;
+	/**
+	 * 考试名称
+	 */
+	private String examName;
+	/**
+	 * 课程名称
+	 */
+	private String courseName;
+	/**
+	 * 课程代码
+	 */
+	private String courseCode;
+
+	private Course course;
+	/**
+	 * 调卷类型 成套调用 重组调用
+	 */
+	private CallType callType;
+	/**
+	 * 是否生成 1:生成 0:没生成 生成预览卷
+	 */
+	private Short ifFinish;
+	/**
+	 * 已经生成的试卷ID Map 数据结构: { "A":"0001", "B":"0002" } A 类型下生成的paperId为 0001 B
+	 * 类型下生成的paperId为 0002
+	 */
+	private Map<String, String> finishedPaperIdMap;
+	/**
+	 * 抽取试卷对象集合
+	 */
+	private List<ExamPaper> examPaperList;
+	/**
+	 * 小题乱序 1:乱序 0:不乱序
+	 */
+	private Short scrambling_the_question_order;
+	/**
+	 * 选项乱序 1:乱序 0:不乱序
+	 */
+	private Short scrambling_the_option_order;
+
+	/**
+	 * 抽卷规则
+	 */
+	private ExtractPolicy policy;
+	/**
+	 * 类型参数
+	 */
+	private Map<String, Object> params;
+	/**
+	 * 机构ID
+	 */
+	private String orgId;
+
+	private String orgName;
+
+	// 随机抽题试卷模板
+	private String randomPaperId;
+
+	// 随机抽题音频播放次数
+	private Integer playTime;
+
+	public ExtractConfig() {
+
+	}
+
+	public ExtractConfig(String id) {
+		this.id = id;
+	}
+
+	public ExtractConfig(Long exam_id, String course_code) {
+		this.examId = exam_id;
+		this.courseCode = course_code;
+	}
+
+	public ExtractConfig(ExamCourseDto examCourseDto) {
+		this.examId = examCourseDto.getExamId();
+		this.examName = examCourseDto.getExamName();
+		this.courseCode = examCourseDto.getCourseCode();
+		this.courseName = examCourseDto.getCourseName();
+		this.examType = examCourseDto.getExamType().name();
+	}
+
+	public Long getExamId() {
+		return examId;
+	}
+
+	public void setExamId(Long examId) {
+		this.examId = examId;
+	}
+
+	public String getCourseCode() {
+		return courseCode;
+	}
+
+	public void setCourseCode(String courseCode) {
+		this.courseCode = courseCode;
+	}
+
+	public ExtractPolicy getPolicy() {
+		return policy;
+	}
+
+	public void setPolicy(ExtractPolicy policy) {
+		this.policy = policy;
+	}
+
+	public String getExamType() {
+		return examType;
+	}
+
+	public void setExamType(String examType) {
+		this.examType = examType;
+	}
+
+	public String getExamName() {
+		return examName;
+	}
+
+	public void setExamName(String examName) {
+		this.examName = examName;
+	}
+
+	public String getCourseName() {
+		return courseName;
+	}
+
+	public void setCourseName(String courseName) {
+		this.courseName = courseName;
+	}
+
+	public CallType getCallType() {
+		return callType;
+	}
+
+	public void setCallType(CallType callType) {
+		this.callType = callType;
+	}
+
+	public Short getIfFinish() {
+		return ifFinish;
+	}
+
+	public void setIfFinish(Short ifFinish) {
+		if (ifFinish == null) {
+			this.ifFinish = 0;
+		} else {
+			this.ifFinish = ifFinish;
+		}
+	}
+
+	public Map<String, Object> getParams() {
+		return params;
+	}
+
+	public void setParams(Map<String, Object> params) {
+		this.params = params;
+	}
+
+	public String getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(String orgId) {
+		this.orgId = orgId;
+	}
+
+	public List<ExamPaper> getExamPaperList() {
+		return examPaperList;
+	}
+
+	public void setExamPaperList(List<ExamPaper> examPaperList) {
+		this.examPaperList = examPaperList;
+	}
+
+	public Short getScrambling_the_question_order() {
+		return scrambling_the_question_order;
+	}
+
+	public void setScrambling_the_question_order(Short scrambling_the_question_order) {
+		if (scrambling_the_question_order == null) {
+			this.scrambling_the_question_order = 0;
+		} else {
+			this.scrambling_the_question_order = scrambling_the_question_order;
+		}
+	}
+
+	public Short getScrambling_the_option_order() {
+		return scrambling_the_option_order;
+	}
+
+	public void setScrambling_the_option_order(Short scrambling_the_option_order) {
+		if (scrambling_the_option_order == null) {
+			this.scrambling_the_option_order = 0;
+		} else {
+			this.scrambling_the_option_order = scrambling_the_option_order;
+		}
+	}
+
+	public Map<String, String> getFinishedPaperIdMap() {
+		return finishedPaperIdMap;
+	}
+
+	public void setFinishedPaperIdMap(Map<String, String> finishedPaperIdMap) {
+		this.finishedPaperIdMap = finishedPaperIdMap;
+	}
+
+	public String getOrgName() {
+		return orgName;
+	}
+
+	public void setOrgName(String orgName) {
+		this.orgName = orgName;
+	}
+
+	public Course getCourse() {
+		return course;
+	}
+
+	public void setCourse(Course course) {
+		this.course = course;
+	}
+
+	public String getRandomPaperId() {
+		return randomPaperId;
+	}
+
+	public void setRandomPaperId(String randomPaperId) {
+		this.randomPaperId = randomPaperId;
+	}
+
+	public Integer getPlayTime() {
+		return playTime;
+	}
+
+	public void setPlayTime(Integer playTime) {
+		this.playTime = playTime;
+	}
+
 }

+ 10 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/PaperSearchInfo.java

@@ -31,6 +31,8 @@ public class PaperSearchInfo implements Serializable {
 	private Integer inUse;
 
 	private Boolean auditStatus;
+	
+	private Boolean fillCount;
 
 	public String getName() {
 		return name;
@@ -128,4 +130,12 @@ public class PaperSearchInfo implements Serializable {
 		this.auditStatus = auditStatus;
 	}
 
+	public Boolean getFillCount() {
+		return fillCount;
+	}
+
+	public void setFillCount(Boolean fillCount) {
+		this.fillCount = fillCount;
+	}
+
 }

+ 96 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/RandomPaper.java

@@ -0,0 +1,96 @@
+package cn.com.qmth.examcloud.core.questions.dao.entity;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperStructType;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+import cn.com.qmth.examcloud.core.questions.dao.entity.base.MongoBaseEntity;
+
+public class RandomPaper extends MongoBaseEntity {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -1357471464371313050L;
+
+	private Long rootOrgId;
+
+	private Long courseId;
+
+	private String name;
+
+	private PaperStructType paperStructType;
+
+	private String paperStructId;
+
+	private PaperType paperType;
+
+	private List<String> paperIds;
+
+	private Boolean enable;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public PaperStructType getPaperStructType() {
+		return paperStructType;
+	}
+
+	public void setPaperStructType(PaperStructType paperStructType) {
+		this.paperStructType = paperStructType;
+	}
+
+	public String getPaperStructId() {
+		return paperStructId;
+	}
+
+	public void setPaperStructId(String paperStructId) {
+		this.paperStructId = paperStructId;
+	}
+
+	public PaperType getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(PaperType paperType) {
+		this.paperType = paperType;
+	}
+
+	public List<String> getPaperIds() {
+		return paperIds;
+	}
+
+	public void setPaperIds(List<String> paperIds) {
+		this.paperIds = paperIds;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+}

+ 126 - 0
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/entity/RandomPaperQuestion.java

@@ -0,0 +1,126 @@
+package cn.com.qmth.examcloud.core.questions.dao.entity;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.entity.base.MongoBaseEntity;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+
+public class RandomPaperQuestion extends MongoBaseEntity {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -1357471464371313050L;
+
+	private Long rootOrgId;
+
+	private String randomPaperId;
+
+	private Long courseId;
+//	 精确结构:大题号-结构序号(1开始)-公开/非公开-难度  |  示例:1-1-true-难
+//	  蓝图结构:大题号-一级属性id-二级属性id-公开/非公开-难度  |  示例:1-1-60efd3e85d030a52bb08b1e8-60efd3e85d030a52bb08b1e9-true-易
+	private String key;
+
+	private String questionId;
+
+	private Double score;
+
+	/**
+	 * 选项数量
+	 */
+	private Integer optionCount;
+
+	/**
+	 * 题型
+	 */
+	private QuesStructType questionType;
+
+	/**
+	 * 作答类型
+	 */
+	private AnswerType answerType;
+
+	private List<RandomPaperQuestion> subQuestions;
+
+	public String getRandomPaperId() {
+		return randomPaperId;
+	}
+
+	public void setRandomPaperId(String randomPaperId) {
+		this.randomPaperId = randomPaperId;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getQuestionId() {
+		return questionId;
+	}
+
+	public void setQuestionId(String questionId) {
+		this.questionId = questionId;
+	}
+
+	public Integer getOptionCount() {
+		return optionCount;
+	}
+
+	public void setOptionCount(Integer optionCount) {
+		this.optionCount = optionCount;
+	}
+
+	public QuesStructType getQuestionType() {
+		return questionType;
+	}
+
+	public void setQuestionType(QuesStructType questionType) {
+		this.questionType = questionType;
+	}
+
+	public AnswerType getAnswerType() {
+		return answerType;
+	}
+
+	public void setAnswerType(AnswerType answerType) {
+		this.answerType = answerType;
+	}
+
+	public List<RandomPaperQuestion> getSubQuestions() {
+		return subQuestions;
+	}
+
+	public void setSubQuestions(List<RandomPaperQuestion> subQuestions) {
+		this.subQuestions = subQuestions;
+	}
+
+	public Double getScore() {
+		return score;
+	}
+
+	public void setScore(Double score) {
+		this.score = score;
+	}
+
+}

+ 5 - 0
examcloud-core-questions-service/pom.xml

@@ -17,6 +17,11 @@
             <artifactId>examcloud-core-questions-dao</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.rpc</groupId>
+            <artifactId>examcloud-core-questions-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 2 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ExtractConfigService.java

@@ -149,4 +149,6 @@ public interface ExtractConfigService {
 
 	List<ExtractConfig> findExtractConfig(Long examId);
 
+	void saveExtractConfigForRandomPaper(ExtractConfig extractConfig);
+
 }

+ 4 - 7
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportDdCollegePaperService.java

@@ -18,9 +18,8 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.poi.xssf.usermodel.XSSFRow;
 import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.bson.types.ObjectId;
 import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.web.multipart.MultipartFile;
@@ -30,7 +29,6 @@ import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.commons.util.Calculator;
 import cn.com.qmth.examcloud.commons.util.JsonUtil;
 import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
 import cn.com.qmth.examcloud.core.questions.base.converter.utils.FileUtil;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperStatus;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
@@ -60,7 +58,6 @@ import cn.com.qmth.examcloud.web.config.SystemProperties;
 @Service
 public class ImportDdCollegePaperService {
 
-    private static final Logger log = LoggerFactory.getLogger(ImportDdCollegePaperService.class);
 
     @Autowired
     private PaperRepo paperRepo;
@@ -397,7 +394,7 @@ public class ImportDdCollegePaperService {
                     TestQuestionInfo testQuestionInfo2 = JsonUtil.fromJson(testQuestion2.getJson(),
                             TestQuestionInfo.class);
                     Question subQuestion = new Question();
-                    subQuestion.setId(IdUtils.uuid());
+                    subQuestion.setId(new ObjectId().toString());
                     subQuestion.setDifficulty("中");
                     subQuestion.setDifficultyDegree(0.5);
                     subQuestion.setPublicity(true);
@@ -790,7 +787,7 @@ public class ImportDdCollegePaperService {
             List<TestFillAnswer> blanks = testQuestionInfo.getBlanks();
             for (TestFillAnswer testFillAnswer : blanks) {
                 Question subQuestion = new Question();
-                subQuestion.setId(IdUtils.uuid());
+                subQuestion.setId(new ObjectId().toString());
                 subQuestion.setQuestionType(QuesStructType.SINGLE_ANSWER_QUESTION);
                 subQuestion.setDifficulty("中");
                 subQuestion.setDifficultyDegree(0.5);
@@ -886,7 +883,7 @@ public class ImportDdCollegePaperService {
         if (blanks != null && blanks.size() > 0) {
             for (TestFillAnswer testFillAnswer : blanks) {
                 Question question = new Question();
-                question.setId(IdUtils.uuid());
+                question.setId(new ObjectId().toString());
                 question.setQuestionType(QuesStructType.SINGLE_ANSWER_QUESTION);
                 question.setDifficulty("中");
                 question.setDifficultyDegree(0.5);

+ 2 - 5
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportPaperFromJsonService.java

@@ -16,8 +16,7 @@ import java.util.stream.Stream;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.bson.types.ObjectId;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Example;
 import org.springframework.stereotype.Service;
@@ -27,7 +26,6 @@ import com.google.gson.reflect.TypeToken;
 
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
 import cn.com.qmth.examcloud.core.questions.base.converter.utils.FileUtil;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperStatus;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
@@ -58,7 +56,6 @@ import cn.com.qmth.examcloud.core.questions.service.impl.CourseService;
 @Service
 public class ImportPaperFromJsonService {
 
-    private static final Logger log = LoggerFactory.getLogger(ImportPaperFromJsonService.class);
 
     @Autowired
     private PaperRepo paperRepo;
@@ -1203,7 +1200,7 @@ public class ImportPaperFromJsonService {
                     throw new PaperException(importPaperCheck.getErrorInfo());
                 }
                 subQues = new Question();
-                subQues.setId(IdUtils.uuid());
+                subQues.setId(new ObjectId().toString());
                 subQues.setQuestionType(getQuesStructType(nestedQuesType));
                 if (StringUtils.isNumeric(importPaperCheck.getQuesScore())) {
                     subQues.setScore(Double.parseDouble(importPaperCheck.getQuesScore()));

+ 3 - 3
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ImportPaperService.java

@@ -24,6 +24,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
 import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
 import org.docx4j.wml.P;
 import org.slf4j.Logger;
@@ -39,7 +40,6 @@ import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 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.CommonUtils;
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
 import cn.com.qmth.examcloud.core.questions.base.IoUtils;
 import cn.com.qmth.examcloud.core.questions.base.Model;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperStatus;
@@ -1349,7 +1349,7 @@ public class ImportPaperService {
                     throw new PaperException(importPaperCheck.getErrorInfo());
                 }
                 subQues = new Question();
-                subQues.setId(IdUtils.uuid());
+                subQues.setId(new ObjectId().toString());
                 subQues.setQuestionType(getQuesStructType(nestedQuesType));
                 if (StringUtils.isNumeric(importPaperCheck.getQuesScore())) {
                     subQues.setScore(Double.parseDouble(importPaperCheck.getQuesScore()));
@@ -1579,7 +1579,7 @@ public class ImportPaperService {
         paper.setCourseNo(courseNo);
         initPaper(paper, paperName, user);
         PaperDetail pd = new PaperDetail();
-        pd.setId(IdUtils.uuid());
+        pd.setId(new ObjectId().toString());
         pd.setNumber(1);
         pd.setPaper(paper);
         pd.setCreator(user.getDisplayName());

+ 2 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperDetailUnitService.java

@@ -99,4 +99,6 @@ public interface PaperDetailUnitService {
     public List<PaperDetailUnit> findByPaperIds(List<String> ids);
 
     public void clearQuestionCache(String questionId);
+
+	boolean paperInUse(String questionId);
 }

+ 3 - 1
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperService.java

@@ -184,7 +184,7 @@ public interface PaperService {
      * @param questionId
      * @return
      */
-    public List<String> deleteImportQuestionById(String detailUnitId, String questionId, User user);
+    public void deleteImportQuestionById(String detailUnitId, String questionId, User user);
 
     /**
      * 向试卷中插入一个试题
@@ -318,4 +318,6 @@ public interface PaperService {
 
 	public List<String> findPaperId(Long fromRootOrgId);
 
+	public void deletePapersPlus(List<String> paperList, User user);
+
 }

+ 149 - 73
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/PaperStructService.java

@@ -16,8 +16,16 @@ import cn.com.qmth.examcloud.core.questions.dao.entity.*;
 import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyNumberDto;
 import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PaperDetailUnitStructDto;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.QuesNameDto;
+import cn.com.qmth.examcloud.core.questions.service.cache.PaperStructCache;
+import cn.com.qmth.examcloud.support.CacheConstants;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageImpl;
@@ -30,6 +38,7 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 /**
@@ -38,17 +47,29 @@ import java.util.stream.Collectors;
 @Service
 public class PaperStructService {
 
+    private static final Logger log = LoggerFactory.getLogger(PaperStructService.class);
+
+    private static Cache<String, PaperStructCache> localPaperStructCache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.MINUTES).build();
+
+    private static int cacheTimeOut = 2 * 60 * 60;
+
+    @Autowired
+    private RandomPaperService randomPaperService;
+
+    @Autowired
+    private PaperStructRepo paperStructRepo;
+
     @Autowired
-    PaperStructRepo paperStructRepo;
+    private QuesTypeNameRepo quesTypeNameRepo;
 
     @Autowired
-    QuesTypeNameRepo quesTypeNameRepo;
+    private MongoTemplate mongoTemplate;
 
     @Autowired
-    MongoTemplate mongoTemplate;
+    private CourseCloudService courseCloudService;
 
     @Autowired
-    CourseCloudService courseCloudService;
+    private RedisClient redisClient;
 
     /**
      * 获取所有试卷结构(分页)
@@ -58,20 +79,20 @@ public class PaperStructService {
      * @param pageSize
      * @return
      */
-    public Page<PaperStruct> getPaperStructs(PaperStructSearchInfo searchInfo, int curPage, int pageSize, UserDataRule userDataRule) {
-         if (userDataRule.assertEmptyQueryResult()) {
-             return Page.empty();
-         }
-
+    public Page<PaperStruct> getPaperStructs(PaperStructSearchInfo searchInfo, int curPage, int pageSize,
+                                             UserDataRule userDataRule) {
+        if (userDataRule.assertEmptyQueryResult()) {
+            return Page.empty();
+        }
 
         Query query = new Query();
         query.addCriteria(Criteria.where("orgId").is(searchInfo.getOrgId()));
         if (userDataRule.assertNeedQueryRefIds()) {
-			Criteria c1 = Criteria.where("courseId").in(userDataRule.getRefIds());
-			Criteria c2 = Criteria.where("courseId").exists(false);
-			Criteria cr = new Criteria();
-			query.addCriteria(cr.orOperator(c1, c2));
-		}
+            Criteria c1 = Criteria.where("courseId").in(userDataRule.getRefIds());
+            Criteria c2 = Criteria.where("courseId").exists(false);
+            Criteria cr = new Criteria();
+            query.addCriteria(cr.orOperator(c1, c2));
+        }
         query.addCriteria(Criteria.where("type").is(searchInfo.getType()));
 
         // 判断试卷结构
@@ -145,22 +166,24 @@ public class PaperStructService {
      * @param searchInfo
      * @return
      */
-    public List<PaperStruct> getPaperStructs(PaperStructSearchInfo searchInfo,UserDataRule ud) {
-    	if (ud.assertEmptyQueryResult()) {
-			return new ArrayList<>();
-		}
+    public List<PaperStruct> getPaperStructs(PaperStructSearchInfo searchInfo, UserDataRule ud) {
+        if (ud.assertEmptyQueryResult()) {
+            return new ArrayList<>();
+        }
         Query query = new Query();
         if (StringUtils.isNotBlank(searchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("courseNo")
-                    .in("", searchInfo.getCourseNo()));
+            query.addCriteria(Criteria.where("courseNo").in("", searchInfo.getCourseNo()));
         }
         query.addCriteria(Criteria.where("orgId").is(searchInfo.getOrgId()));
-		if (ud.assertNeedQueryRefIds()) {
-			Criteria c1 = Criteria.where("courseId").in(ud.getRefIds());
-			Criteria c2 = Criteria.where("courseId").exists(false);
-			Criteria cr = new Criteria();
-			cr.orOperator(c1, c2);
-		}
+        if (ud.assertNeedQueryRefIds()) {
+            Criteria c1 = Criteria.where("courseId").in(ud.getRefIds());
+            Criteria c2 = Criteria.where("courseId").exists(false);
+            Criteria cr = new Criteria();
+            cr.orOperator(c1, c2);
+        }
+        if (StringUtils.isNotBlank(searchInfo.getType())) {
+            query.addCriteria(Criteria.where("type").is(searchInfo.getType()));
+        }
         query.with(Sort.by(Sort.Order.desc("createTime")));
         List<PaperStruct> paperList = this.mongoTemplate.find(query, PaperStruct.class);
         return paperList;
@@ -189,23 +212,24 @@ public class PaperStructService {
      * @return
      */
     public PaperStruct save(PaperStruct paperStruct, User user) {
-    	if (StringUtils.isNotBlank(paperStruct.getCourseNo())) {
-    		GetCourseReq req = new GetCourseReq();
+        if (StringUtils.isNotBlank(paperStruct.getCourseNo())) {
+            GetCourseReq req = new GetCourseReq();
             req.setRootOrgId(Long.valueOf(paperStruct.getOrgId()));
             req.setCode(paperStruct.getCourseNo());
             GetCourseResp resp = courseCloudService.getCourse(req);
-			if (resp.getCourseBean() == null) {
-				throw new StatusException("500","课程不存在");
-			}
-			paperStruct.setCourseId(resp.getCourseBean().getId());
-		} else {
-			paperStruct.setCourseId(null);
-		}
+            if (resp.getCourseBean() == null) {
+                throw new StatusException("500", "课程不存在");
+            }
+            paperStruct.setCourseId(resp.getCourseBean().getId());
+        } else {
+            paperStruct.setCourseId(null);
+        }
         if (StringUtils.isNotBlank(paperStruct.getId())) {
             PaperStruct oldPaperStruct = Model.of(paperStructRepo.findById(paperStruct.getId()));
             PaperStruct rps = null;
             if (oldPaperStruct != null && !paperStruct.getName().equals(oldPaperStruct.getName())) {// 那么就是更新操作
-                rps = this.checkNameUnique(paperStruct.getName(), user.getRootOrgId().toString(), oldPaperStruct.getType());
+                rps = this.checkNameUnique(paperStruct.getName(), user.getRootOrgId().toString(),
+                        oldPaperStruct.getType());
             }
             if (rps != null) {
                 return null;
@@ -213,7 +237,7 @@ public class PaperStructService {
         }
         List<PaperDetailStruct> paperDetailStructs = paperStruct.getPaperDetailStructs();
         int number = 0;
-        //新增精确试卷结构
+        // 新增精确试卷结构
         if (paperStruct.getType().equals("EXACT")) {
             for (PaperDetailStruct paperDetailStruct : paperDetailStructs) {
                 List<PaperDetailUnitStruct> oldStructs = paperDetailStruct.getPaperDetailUnitStructs();
@@ -222,7 +246,7 @@ public class PaperStructService {
                 }
                 List<PaperDetailUnitStruct> unitStructs = new ArrayList<>();
                 for (PaperDetailUnitStructDto unitStructDto : paperDetailStruct.getUnitStructs()) {
-                    //公开简单总数
+                    // 公开简单总数
                     for (int i = 0; i < unitStructDto.getPublicSimple(); i++) {
                         ++number;
                         PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct();
@@ -236,7 +260,7 @@ public class PaperStructService {
                         unitStruct.setPropertyGroup(buildGroup(unitStruct));
                         unitStructs.add(unitStruct);
                     }
-                    //公开中等总数
+                    // 公开中等总数
                     for (int i = 0; i < unitStructDto.getPublicMedium(); i++) {
                         ++number;
                         PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct();
@@ -250,7 +274,7 @@ public class PaperStructService {
                         unitStruct.setPropertyGroup(buildGroup(unitStruct));
                         unitStructs.add(unitStruct);
                     }
-                    //公开困难总数
+                    // 公开困难总数
                     for (int i = 0; i < unitStructDto.getPublicDifficulty(); i++) {
                         ++number;
                         PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct();
@@ -264,7 +288,7 @@ public class PaperStructService {
                         unitStruct.setPropertyGroup(buildGroup(unitStruct));
                         unitStructs.add(unitStruct);
                     }
-                    //非公开简单总数
+                    // 非公开简单总数
                     for (int i = 0; i < unitStructDto.getNoPublicSimple(); i++) {
                         ++number;
                         PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct();
@@ -278,7 +302,7 @@ public class PaperStructService {
                         unitStruct.setPropertyGroup(buildGroup(unitStruct));
                         unitStructs.add(unitStruct);
                     }
-                    //非公开中等总数
+                    // 非公开中等总数
                     for (int i = 0; i < unitStructDto.getNoPublicMedium(); i++) {
                         ++number;
                         PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct();
@@ -292,7 +316,7 @@ public class PaperStructService {
                         unitStruct.setPropertyGroup(buildGroup(unitStruct));
                         unitStructs.add(unitStruct);
                     }
-                    //非公开困难总数
+                    // 非公开困难总数
                     for (int i = 0; i < unitStructDto.getNoPublicDifficulty(); i++) {
                         ++number;
                         PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct();
@@ -310,67 +334,75 @@ public class PaperStructService {
                 paperDetailStruct.setPaperDetailUnitStructs(unitStructs);
             }
             paperStruct.setPaperStrucType(PaperStructType.EXACT);
-            paperStruct.setDetailUnitCount(paperDetailStructs.stream().mapToInt(PaperDetailStruct::getDetailCount).sum());
+            paperStruct
+                    .setDetailUnitCount(paperDetailStructs.stream().mapToInt(PaperDetailStruct::getDetailCount).sum());
         } else {
-            //新增蓝图试卷结构
+            // 新增蓝图试卷结构
             for (PaperDetailStruct paperDetailStruct : paperDetailStructs) {
                 List<PaperDetailUnitStruct> oldStructs = paperDetailStruct.getPaperDetailUnitStructs();
                 if (oldStructs != null && oldStructs.size() > 0) {
                     oldStructs.clear();
                 }
                 List<PaperDetailUnitStruct> unitStructs = new ArrayList<>();
-                for (CoursePropertyNumberDto coursePropertyNumberDto : paperDetailStruct.getCoursePropertyNumberDtos()) {
-                    //取到子节点上的题目
+                for (CoursePropertyNumberDto coursePropertyNumberDto : paperDetailStruct
+                        .getCoursePropertyNumberDtos()) {
+                    // 取到子节点上的题目
                     if (!coursePropertyNumberDto.getDisable()) {
-                        //公开简单数量
+                        // 公开简单数量
                         for (int i = 0; i < coursePropertyNumberDto.getPublicSimple(); i++) {
                             ++number;
-                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number, coursePropertyNumberDto);
+                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number,
+                                    coursePropertyNumberDto);
                             unitStruct.setDifficulty("易");
                             unitStruct.setPublicity(true);
                             unitStruct.setPropertyGroup(buildPropertyGroup(unitStruct));
                             unitStructs.add(unitStruct);
                         }
-                        //公开中等数量
+                        // 公开中等数量
                         for (int i = 0; i < coursePropertyNumberDto.getPublicMedium(); i++) {
                             ++number;
-                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number, coursePropertyNumberDto);
+                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number,
+                                    coursePropertyNumberDto);
                             unitStruct.setDifficulty("中");
                             unitStruct.setPublicity(true);
                             unitStruct.setPropertyGroup(buildPropertyGroup(unitStruct));
                             unitStructs.add(unitStruct);
                         }
-                        //公开困难数量
+                        // 公开困难数量
                         for (int i = 0; i < coursePropertyNumberDto.getPublicDifficulty(); i++) {
                             ++number;
-                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number, coursePropertyNumberDto);
+                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number,
+                                    coursePropertyNumberDto);
                             unitStruct.setDifficulty("难");
                             unitStruct.setPublicity(true);
                             unitStruct.setPropertyGroup(buildPropertyGroup(unitStruct));
                             unitStructs.add(unitStruct);
                         }
-                        //非公开简单数量
+                        // 非公开简单数量
                         for (int i = 0; i < coursePropertyNumberDto.getNoPublicSimple(); i++) {
                             ++number;
-                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number, coursePropertyNumberDto);
+                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number,
+                                    coursePropertyNumberDto);
                             unitStruct.setDifficulty("易");
                             unitStruct.setPublicity(false);
                             unitStruct.setPropertyGroup(buildPropertyGroup(unitStruct));
                             unitStructs.add(unitStruct);
                         }
-                        //非公开中等数量
+                        // 非公开中等数量
                         for (int i = 0; i < coursePropertyNumberDto.getNoPublicMedium(); i++) {
                             ++number;
-                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number, coursePropertyNumberDto);
+                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number,
+                                    coursePropertyNumberDto);
                             unitStruct.setDifficulty("中");
                             unitStruct.setPublicity(false);
                             unitStruct.setPropertyGroup(buildPropertyGroup(unitStruct));
                             unitStructs.add(unitStruct);
                         }
-                        //非公开困难
+                        // 非公开困难
                         for (int i = 0; i < coursePropertyNumberDto.getNoPublicDifficulty(); i++) {
                             ++number;
-                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number, coursePropertyNumberDto);
+                            PaperDetailUnitStruct unitStruct = new PaperDetailUnitStruct(paperDetailStruct, number,
+                                    coursePropertyNumberDto);
                             unitStruct.setDifficulty("难");
                             unitStruct.setPublicity(false);
                             unitStruct.setPropertyGroup(buildPropertyGroup(unitStruct));
@@ -381,7 +413,7 @@ public class PaperStructService {
                 paperDetailStruct.setPaperDetailUnitStructs(unitStructs);
             }
             paperStruct.setPaperStrucType(PaperStructType.BLUEPRINT);
-            //计算试卷结构难度
+            // 计算试卷结构难度
             Double difficulty = getDifficulty(paperDetailStructs, paperStruct.getTotalScore());
             paperStruct.setDifficulty(difficulty);
         }
@@ -389,7 +421,10 @@ public class PaperStructService {
         paperStruct.setCreator(user.getDisplayName());
         paperStruct.setCreateTime(CommonUtils.getCurDateTime());
         paperStruct.setDetailCount(paperDetailStructs.size());
-        return paperStructRepo.save(paperStruct);
+        PaperStruct ret = paperStructRepo.save(paperStruct);
+        String key = CacheConstants.CACHE_Q_PAPER_STRUCT + paperStruct.getId();
+        redisClient.delete(key);
+        return ret;
     }
 
     /**
@@ -408,7 +443,8 @@ public class PaperStructService {
      * @param unitStruct
      */
     private String buildPropertyGroup(PaperDetailUnitStruct unitStruct) {
-        return unitStruct.getPropertyId() + "-" + String.valueOf(unitStruct.getPublicity()) + "-" + unitStruct.getDifficulty();
+        return unitStruct.getPropertyId() + "-" + String.valueOf(unitStruct.getPublicity()) + "-"
+                + unitStruct.getDifficulty();
     }
 
     public Double getDifficulty(List<PaperDetailStruct> paperDetailStructs, Double totalScore) {
@@ -420,8 +456,11 @@ public class PaperStructService {
             for (PaperDetailStruct paperDetailStruct : paperDetailStructs) {
                 simpleCount = paperDetailStruct.getPublicSimpleCount() + paperDetailStruct.getNoPublicSimpleCount();
                 mediumCount = paperDetailStruct.getPublicMediumCount() + paperDetailStruct.getNoPublicMediumCount();
-                difficultyCount = paperDetailStruct.getPublicDifficultyCount() + paperDetailStruct.getNoPublicDifficultyCount();
-                sum = simpleCount * paperDetailStruct.getScore() * 0.8 + mediumCount * paperDetailStruct.getScore() * 0.5 + difficultyCount * paperDetailStruct.getScore() * 0.2 + sum;
+                difficultyCount = paperDetailStruct.getPublicDifficultyCount()
+                        + paperDetailStruct.getNoPublicDifficultyCount();
+                sum = simpleCount * paperDetailStruct.getScore() * 0.8
+                        + mediumCount * paperDetailStruct.getScore() * 0.5
+                        + difficultyCount * paperDetailStruct.getScore() * 0.2 + sum;
             }
 
             Double dif = sum / totalScore;
@@ -441,9 +480,7 @@ public class PaperStructService {
         return paperStruct;
     }
 
-    public List<QuesNameDto> getQuesNameList(String orgId,
-                                             String courseNo,
-                                             QuesStructType quesType) {
+    public List<QuesNameDto> getQuesNameList(String orgId, String courseNo, QuesStructType quesType) {
         List<QuesNameDto> quesNameList = new ArrayList<>();
         List<QuesTypeName> quesTypeNames = new ArrayList<>();
         if (StringUtils.isEmpty(courseNo)) {
@@ -451,12 +488,8 @@ public class PaperStructService {
         } else {
             quesTypeNames = quesTypeNameRepo.findQuesName(orgId, courseNo, quesType);
         }
-        quesNameList = quesTypeNames.stream()
-                .map(QuesTypeName::getQuesNames)
-                .flatMap(Collection::stream)
-                .distinct()
-                .map(this::getQuesName)
-                .collect(Collectors.toList());
+        quesNameList = quesTypeNames.stream().map(QuesTypeName::getQuesNames).flatMap(Collection::stream).distinct()
+                .map(this::getQuesName).collect(Collectors.toList());
         return quesNameList;
     }
 
@@ -464,4 +497,47 @@ public class PaperStructService {
         return new QuesNameDto(name, name);
     }
 
+    public PaperStructCache getPaperStructCacheById(String paperStructId) {
+        // 组卷结构 优先从本地缓存中获取
+        String key = CacheConstants.CACHE_Q_PAPER_STRUCT + paperStructId;
+        PaperStructCache ps = localPaperStructCache.getIfPresent(key);
+        if (ps != null) {
+            log.warn("从【本地缓存】中获取组卷结构! key:{}", key);
+            return ps;
+        }
+
+        // 从redis缓存中获取
+        ps = redisClient.get(key, PaperStructCache.class, cacheTimeOut);
+        if (ps != null) {
+            localPaperStructCache.put(key, ps);
+            log.warn("从【Redis缓存】中获取抽卷模板! key:{}", key);
+            return ps;
+        }
+
+        // 从数据库中获取
+        Query query = new Query();
+        query.addCriteria(Criteria.where("_id").is(new ObjectId(paperStructId)));
+        ps = this.mongoTemplate.findOne(query, PaperStructCache.class, "paperStruct");
+        if (ps == null) {
+            throw new StatusException("未找到试卷结构:" + paperStructId);
+        }
+
+        log.warn("从【数据库】中获取抽卷模板! key:{}", key);
+        redisClient.set(key, ps, cacheTimeOut);
+        localPaperStructCache.put(key, ps);
+        return ps;
+    }
+
+    public void removePaperStruct(List<String> ids) {
+        for (String id : ids) {
+            if (randomPaperService.existStruct(id)) {
+                PaperStruct ps = Model.of(paperStructRepo.findById(id));
+                throw new StatusException(ps.getName() + "已被抽题模板使用,不能删除");
+            }
+            paperStructRepo.deleteById(id);
+            String key = CacheConstants.CACHE_Q_PAPER_STRUCT + id;
+            redisClient.delete(key);
+        }
+    }
+
 }

+ 2 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/QuestionAudioService.java

@@ -63,5 +63,7 @@ public interface QuestionAudioService {
      * 根据试题集合删除音频
      */
     public void deleteAudio(List<Question> questions);
+
+	void deleteAudioByQuestionId(List<String> questionIds);
 }
 

+ 12 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/RandomPaperQuestionService.java

@@ -0,0 +1,12 @@
+package cn.com.qmth.examcloud.core.questions.service;
+
+import java.util.List;
+
+public interface RandomPaperQuestionService {
+
+	boolean existQuestion(String questionId);
+
+	boolean existQuestion(List<String> questionIds);
+
+
+}

+ 34 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/RandomPaperService.java

@@ -0,0 +1,34 @@
+package cn.com.qmth.examcloud.core.questions.service;
+
+import org.springframework.data.domain.Page;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.PaperQuestionViewQuery;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperDomain;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperListVo;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperQuery;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.StructInfo;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
+
+public interface RandomPaperService {
+
+	Page<RandomPaperListVo> getPage(RandomPaperQuery query);
+
+	void toggle(String id, Boolean enable, User accessUser);
+
+	StructInfo getStructQuestionInfo(String structId);
+
+	StructInfo saveRandomPaper(RandomPaperDomain domain);
+
+	StructInfo getPaperQuestionViewInfo(PaperQuestionViewQuery query);
+
+	RandomPaperListVo getInfo(String id);
+
+	DefaultPaper getRandomPaper(String randomPaperId, Integer playTime);
+
+	boolean existStruct(String id);
+
+	boolean existPaper(Long courseId,String id);
+
+
+}

+ 14 - 5
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/context/DetailContext.java

@@ -1,10 +1,19 @@
 package cn.com.qmth.examcloud.core.questions.service.bean.context;
 
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
-import cn.com.qmth.examcloud.core.questions.dao.entity.*;
-import cn.com.qmth.examcloud.core.questions.service.bean.condition.Condition;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.bson.types.ObjectId;
 
-import java.util.*;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailStruct;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnitStruct;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.service.bean.condition.Condition;
 
 public class DetailContext extends Context {
 
@@ -41,7 +50,7 @@ public class DetailContext extends Context {
         Map<PaperDetail, List<PaperDetailUnit>> detailMap = new HashMap<>();
         PaperDetail pd = new PaperDetail();
         List<PaperDetailUnit> unitList = new ArrayList<>();
-        pd.setId(IdUtils.uuid());
+        pd.setId(new ObjectId().toString());
         for (int i = 0; i < units.size(); i++) {
             UnitContext uc = units.get(i);
             PaperDetailUnit pdu = uc.build(uc.getUnitStruct());

+ 12 - 7
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/context/PaperContext.java

@@ -1,16 +1,21 @@
 package cn.com.qmth.examcloud.core.questions.service.bean.context;
 
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
-import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
-import cn.com.qmth.examcloud.core.questions.dao.entity.*;
-import cn.com.qmth.examcloud.core.questions.service.bean.condition.Condition;
-import cn.com.qmth.examcloud.core.questions.service.util.PaperUtil;
-
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
+import org.bson.types.ObjectId;
+
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetail;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailStruct;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperStruct;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
+import cn.com.qmth.examcloud.core.questions.service.bean.condition.Condition;
+
 public class PaperContext extends Context {
 
     private PaperStruct paperStruct;
@@ -37,7 +42,7 @@ public class PaperContext extends Context {
     public List<Object> build(PaperStruct paperStruct) {
         List<Object> list = new ArrayList<>();
         Paper paper = new Paper();
-        paper.setId(IdUtils.uuid());
+        paper.setId(new ObjectId().toString());
         paper.setPaperDetailCount(details.size());
         paper.setTotalScore(paperStruct.getTotalScore());
         paper.setPaperType(PaperType.GENERATE);

+ 5 - 4
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/context/UnitContext.java

@@ -1,14 +1,15 @@
 package cn.com.qmth.examcloud.core.questions.service.bean.context;
 
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
+import java.util.List;
+
+import org.bson.types.ObjectId;
+
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
 import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
 import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnitStruct;
 import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
 import cn.com.qmth.examcloud.core.questions.service.bean.condition.Condition;
 
-import java.util.List;
-
 public class UnitContext extends Context {
 
     private DetailContext detailContext;
@@ -37,7 +38,7 @@ public class UnitContext extends Context {
 
     public PaperDetailUnit build(PaperDetailUnitStruct unitStruct) {
         PaperDetailUnit pdu = new PaperDetailUnit();
-        pdu.setId(IdUtils.uuid());
+        pdu.setId(new ObjectId().toString());
         pdu.setQuestionType(unitStruct.getQuestionType());
         pdu.setScore(unitStruct.getScore());
         pdu.setNumber(unitStruct.getNumber());

+ 28 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/dto/PaperDetailUnitDto.java

@@ -0,0 +1,28 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.dto;
+
+import org.springframework.data.mongodb.core.mapping.DBRef;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.Paper;
+
+public class PaperDetailUnitDto {
+	private String id;
+	@DBRef(lazy = false)
+	private Paper paper;// 关联的试卷
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public Paper getPaper() {
+		return paper;
+	}
+
+	public void setPaper(Paper paper) {
+		this.paper = paper;
+	}
+
+}

+ 83 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/CreateDefaultPaperParam.java

@@ -0,0 +1,83 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyNumberDto;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PaperDetailUnitStructDto;
+import cn.com.qmth.examcloud.core.questions.service.cache.RandomPaperCache;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionStructureWrapper;
+
+public class CreateDefaultPaperParam {
+	private PaperDetailUnitStructDto us;
+	//精确结构大题下的一行
+	private Integer index;
+	private Boolean fullyObjective;
+	private String key;
+	private Integer unitCount;
+	private Integer playTime;
+	private Integer detailNumber;
+	private List<DefaultQuestionStructureWrapper> units;
+	private CoursePropertyNumberDto cp;
+	private RandomPaperCache rp;
+	public Integer getPlayTime() {
+		return playTime;
+	}
+	public void setPlayTime(Integer playTime) {
+		this.playTime = playTime;
+	}
+	public Integer getDetailNumber() {
+		return detailNumber;
+	}
+	public void setDetailNumber(Integer detailNumber) {
+		this.detailNumber = detailNumber;
+	}
+	public List<DefaultQuestionStructureWrapper> getUnits() {
+		return units;
+	}
+	public void setUnits(List<DefaultQuestionStructureWrapper> units) {
+		this.units = units;
+	}
+	public CoursePropertyNumberDto getCp() {
+		return cp;
+	}
+	public void setCp(CoursePropertyNumberDto cp) {
+		this.cp = cp;
+	}
+	public RandomPaperCache getRp() {
+		return rp;
+	}
+	public void setRp(RandomPaperCache rp) {
+		this.rp = rp;
+	}
+	public Integer getUnitCount() {
+		return unitCount;
+	}
+	public void setUnitCount(Integer unitCount) {
+		this.unitCount = unitCount;
+	}
+	public String getKey() {
+		return key;
+	}
+	public void setKey(String key) {
+		this.key = key;
+	}
+	public Boolean getFullyObjective() {
+		return fullyObjective;
+	}
+	public void setFullyObjective(Boolean fullyObjective) {
+		this.fullyObjective = fullyObjective;
+	}
+	public Integer getIndex() {
+		return index;
+	}
+	public void setIndex(Integer index) {
+		this.index = index;
+	}
+	public PaperDetailUnitStructDto getUs() {
+		return us;
+	}
+	public void setUs(PaperDetailUnitStructDto us) {
+		this.us = us;
+	}
+	
+}

+ 95 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperDetailDto.java

@@ -0,0 +1,95 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+public class PaperDetailDto {
+	private String id;
+
+    private Integer number;// 大题序号
+
+    private String name;// 大题名称
+
+    private Double score;// 大题分数
+
+    private Integer unitCount;// 大题下的小题数量
+
+    private String creator;// 创建人id
+
+    private String createTime;// 创建时间
+    
+    //复制数据用
+    private Long orgId;
+    
+
+
+    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 getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    public Integer getUnitCount() {
+        return unitCount;
+    }
+
+    public void setUnitCount(Integer unitCount) {
+        this.unitCount = unitCount;
+    }
+
+    public String getCreator() {
+        return creator;
+    }
+
+    public void setCreator(String creator) {
+        this.creator = creator;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+
+    public PaperDetailDto() {
+
+    }
+
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	
+    
+}

+ 115 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperDetailUnitDto.java

@@ -0,0 +1,115 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import org.springframework.data.mongodb.core.mapping.DBRef;
+
+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.entity.base.MongoBaseEntity;
+
+public class PaperDetailUnitDto extends MongoBaseEntity {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 266448283780973578L;
+
+	private Integer number;// 小题序号
+
+	@DBRef(lazy = true)
+	private PaperDetailDto paperDetail;// 关联的大题
+
+	private QuesStructType questionType;// 小题类型
+
+	@DBRef(lazy = true)
+	private QuestionDto question;// 关联试题
+
+	private String creator;// 创建人id
+
+	private String createTime;// 创建时间
+
+	private PaperType paperType;
+	// 复制数据用
+	private Long orgId;
+	/**
+	 * 作答限时 K12
+	 */
+	private Long timeLimit;
+
+	public PaperDetailUnitDto() {
+	}
+
+	public Integer getNumber() {
+		return number;
+	}
+
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+
+
+	public PaperDetailDto getPaperDetail() {
+		return paperDetail;
+	}
+
+	public void setPaperDetail(PaperDetailDto paperDetail) {
+		this.paperDetail = paperDetail;
+	}
+
+	public QuesStructType getQuestionType() {
+		return questionType;
+	}
+
+	public void setQuestionType(QuesStructType questionType) {
+		this.questionType = questionType;
+	}
+
+	public QuestionDto getQuestion() {
+		return question;
+	}
+
+	public void setQuestion(QuestionDto question) {
+		this.question = question;
+	}
+
+	public String getCreator() {
+		return creator;
+	}
+
+	public void setCreator(String creator) {
+		this.creator = creator;
+	}
+
+	public String getCreateTime() {
+		return createTime;
+	}
+
+	public void setCreateTime(String createTime) {
+		this.createTime = createTime;
+	}
+
+	public PaperType getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(PaperType paperType) {
+		this.paperType = paperType;
+	}
+
+	public Long getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(Long orgId) {
+		this.orgId = orgId;
+	}
+
+	public Long getTimeLimit() {
+		return timeLimit;
+	}
+
+	public void setTimeLimit(Long timeLimit) {
+		this.timeLimit = timeLimit;
+	}
+
+
+}

+ 21 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperQuestionViewQuery.java

@@ -0,0 +1,21 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.List;
+
+public class PaperQuestionViewQuery {
+	private String structId;
+	private List<String> paperIds;
+	public String getStructId() {
+		return structId;
+	}
+	public void setStructId(String structId) {
+		this.structId = structId;
+	}
+	public List<String> getPaperIds() {
+		return paperIds;
+	}
+	public void setPaperIds(List<String> paperIds) {
+		this.paperIds = paperIds;
+	}
+	
+}

+ 26 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/PaperVo.java

@@ -0,0 +1,26 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+public class PaperVo {
+	private String id;
+	private String name;
+	private String unitCount;
+	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 getUnitCount() {
+		return unitCount;
+	}
+	public void setUnitCount(String unitCount) {
+		this.unitCount = unitCount;
+	}
+	
+}

+ 22 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/QuesOptionDto.java

@@ -0,0 +1,22 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+
+public class QuesOptionDto{
+
+
+    private String number;
+
+
+    public String getNumber() {
+        return number;
+    }
+
+    public void setNumber(String number) {
+        this.number = number;
+    }
+
+
+    public QuesOptionDto() {
+    }
+
+}

+ 156 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/QuestionDto.java

@@ -0,0 +1,156 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
+import cn.com.qmth.examcloud.core.questions.dao.entity.QuesProperty;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
+
+public class QuestionDto {
+
+	private String id;
+
+	private QuesStructType questionType;// 试题结构类型
+	/*
+	 * 作答类型
+	 */
+	private AnswerType answerType;
+
+	private Course course;
+
+	private String orgId; // 机构ID
+
+	private String quesName;// 来源大题
+
+	private Integer number;
+
+	private List<QuesOptionDto> quesOptions;// 试题选项
+
+	private List<QuestionDto> subQuestions;// 子题目,用于套题
+	/**
+	 * 试题属性
+	 */
+	private List<QuesProperty> quesProperties;// 关联多组属性
+
+	private Double difficultyDegree; // 难度系数
+
+	private Boolean publicity; // 公开度
+
+	private String difficulty;// 难度
+
+	private List<String> propertyGroup;// 试题属性组合(蓝图组卷使用)
+
+	public QuesStructType getQuestionType() {
+		return questionType;
+	}
+
+	public void setQuestionType(QuesStructType questionType) {
+		this.questionType = questionType;
+	}
+
+	public Course getCourse() {
+		return course;
+	}
+
+	public void setCourse(Course course) {
+		this.course = course;
+	}
+
+	public String getOrgId() {
+		return orgId;
+	}
+
+	public void setOrgId(String orgId) {
+		this.orgId = orgId;
+	}
+
+	public String getQuesName() {
+		return quesName;
+	}
+
+	public void setQuesName(String quesName) {
+		this.quesName = quesName;
+	}
+
+	public Integer getNumber() {
+		return number;
+	}
+
+	public void setNumber(Integer number) {
+		this.number = number;
+	}
+
+	public List<QuesProperty> getQuesProperties() {
+		return quesProperties;
+	}
+
+	public void setQuesProperties(List<QuesProperty> quesProperties) {
+		this.quesProperties = quesProperties;
+	}
+
+	public Double getDifficultyDegree() {
+		return difficultyDegree;
+	}
+
+	public void setDifficultyDegree(Double difficultyDegree) {
+		this.difficultyDegree = difficultyDegree;
+	}
+
+	public Boolean getPublicity() {
+		return publicity;
+	}
+
+	public void setPublicity(Boolean publicity) {
+		this.publicity = publicity;
+	}
+
+	public String getDifficulty() {
+		return difficulty;
+	}
+
+	public void setDifficulty(String difficulty) {
+		this.difficulty = difficulty;
+	}
+
+	public List<String> getPropertyGroup() {
+		return propertyGroup;
+	}
+
+	public void setPropertyGroup(List<String> propertyGroup) {
+		this.propertyGroup = propertyGroup;
+	}
+
+	public List<QuestionDto> getSubQuestions() {
+		return subQuestions;
+	}
+
+	public void setSubQuestions(List<QuestionDto> subQuestions) {
+		this.subQuestions = subQuestions;
+	}
+
+	public List<QuesOptionDto> getQuesOptions() {
+		return quesOptions;
+	}
+
+	public void setQuesOptions(List<QuesOptionDto> quesOptions) {
+		this.quesOptions = quesOptions;
+	}
+
+	public AnswerType getAnswerType() {
+		return answerType;
+	}
+
+	public void setAnswerType(AnswerType answerType) {
+		this.answerType = answerType;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+}

+ 99 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperDomain.java

@@ -0,0 +1,99 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperStructType;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+
+public class RandomPaperDomain {
+	private String id;
+
+	private Long rootOrgId;
+
+	private Long courseId;
+
+	private String name;
+
+	private PaperStructType paperStructType;
+
+	private String paperStructId;
+
+	private PaperType paperType;
+
+	private List<String> paperIds;
+
+	private Boolean enable;
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public PaperStructType getPaperStructType() {
+		return paperStructType;
+	}
+
+	public void setPaperStructType(PaperStructType paperStructType) {
+		this.paperStructType = paperStructType;
+	}
+
+	public String getPaperStructId() {
+		return paperStructId;
+	}
+
+	public void setPaperStructId(String paperStructId) {
+		this.paperStructId = paperStructId;
+	}
+
+	public PaperType getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(PaperType paperType) {
+		this.paperType = paperType;
+	}
+
+	public List<String> getPaperIds() {
+		return paperIds;
+	}
+
+	public void setPaperIds(List<String> paperIds) {
+		this.paperIds = paperIds;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+}

+ 183 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperListVo.java

@@ -0,0 +1,183 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.Date;
+import java.util.List;
+
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperStructType;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
+import io.swagger.annotations.ApiModelProperty;
+
+public class RandomPaperListVo {
+
+	@ApiModelProperty("id")
+	private String id;
+	@ApiModelProperty("模板名称")
+	private String name;
+
+	private Long courseId;
+	@ApiModelProperty("课程代码")
+	private String courseCode;
+	@ApiModelProperty("课程名称")
+	private String courseName;
+
+	private PaperStructType paperStructType;
+	@ApiModelProperty("组卷类型")
+	private String paperStructTypeStr;
+
+	@ApiModelProperty("组卷结构id")
+	private String paperStructId;
+
+	@ApiModelProperty("结构名称")
+	private String paperStructName;
+	private Boolean enable;
+	@ApiModelProperty("状态")
+	private String enableStr;
+	@ApiModelProperty("更新时间")
+	private Date updateDate;
+	@ApiModelProperty("操作人")
+	private String updateByName;
+	private Long updateBy;
+
+	private PaperType paperType;
+
+	private List<PaperVo> papers;
+	
+	private List<String> paperIds;
+
+	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 Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	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 PaperStructType getPaperStructType() {
+		return paperStructType;
+	}
+
+	public void setPaperStructType(PaperStructType paperStructType) {
+		this.paperStructType = paperStructType;
+	}
+
+	public String getPaperStructTypeStr() {
+		return paperStructTypeStr;
+	}
+
+	public void setPaperStructTypeStr(String paperStructTypeStr) {
+		this.paperStructTypeStr = paperStructTypeStr;
+	}
+
+	public String getPaperStructId() {
+		return paperStructId;
+	}
+
+	public void setPaperStructId(String paperStructId) {
+		this.paperStructId = paperStructId;
+	}
+
+	public String getPaperStructName() {
+		return paperStructName;
+	}
+
+	public void setPaperStructName(String paperStructName) {
+		this.paperStructName = paperStructName;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public String getEnableStr() {
+		return enableStr;
+	}
+
+	public void setEnableStr(String enableStr) {
+		this.enableStr = enableStr;
+	}
+
+	public Date getUpdateDate() {
+		return updateDate;
+	}
+
+	public void setUpdateDate(Date updateDate) {
+		this.updateDate = updateDate;
+	}
+
+	public String getUpdateByName() {
+		return updateByName;
+	}
+
+	public void setUpdateByName(String updateByName) {
+		this.updateByName = updateByName;
+	}
+
+	public Long getUpdateBy() {
+		return updateBy;
+	}
+
+	public void setUpdateBy(Long updateBy) {
+		this.updateBy = updateBy;
+	}
+
+	public PaperType getPaperType() {
+		return paperType;
+	}
+
+	public void setPaperType(PaperType paperType) {
+		this.paperType = paperType;
+	}
+
+	public List<PaperVo> getPapers() {
+		return papers;
+	}
+
+	public void setPapers(List<PaperVo> papers) {
+		this.papers = papers;
+	}
+
+	public List<String> getPaperIds() {
+		return paperIds;
+	}
+
+	public void setPaperIds(List<String> paperIds) {
+		this.paperIds = paperIds;
+	}
+
+}

+ 75 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperQuery.java

@@ -0,0 +1,75 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
+import cn.com.qmth.examcloud.core.questions.base.bean.PagerQuery;
+import io.swagger.annotations.ApiModelProperty;
+
+public class RandomPaperQuery extends PagerQuery {
+
+	@ApiModelProperty("课程id")
+	private Long courseId;
+
+	@ApiModelProperty("模板名称")
+	private String name;
+
+	@ApiModelProperty("状态")
+	private Boolean enable;
+	
+	@ApiModelProperty("ID")
+	private String id;
+
+	@ApiModelProperty(hidden = true)
+	private Long rootOrgId;
+
+	@ApiModelProperty(hidden = true)
+	private UserDataRule ud;
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	public Boolean getEnable() {
+		return enable;
+	}
+
+	public void setEnable(Boolean enable) {
+		this.enable = enable;
+	}
+
+	public Long getRootOrgId() {
+		return rootOrgId;
+	}
+
+	public void setRootOrgId(Long rootOrgId) {
+		this.rootOrgId = rootOrgId;
+	}
+
+	public UserDataRule getUd() {
+		return ud;
+	}
+
+	public void setUd(UserDataRule ud) {
+		this.ud = ud;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+}

+ 47 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/RandomPaperQuestionDto.java

@@ -0,0 +1,47 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RandomPaperQuestionDto {
+	private Integer detailNumber;
+
+	private Double unitScore;
+
+	private String key;
+
+	private List<QuestionDto> questionDtos = new ArrayList<>();
+
+	public String getKey() {
+		return key;
+	}
+
+	public void setKey(String key) {
+		this.key = key;
+	}
+
+	public List<QuestionDto> getQuestionDtos() {
+		return questionDtos;
+	}
+
+	public void setQuestionDtos(List<QuestionDto> questionDtos) {
+		this.questionDtos = questionDtos;
+	}
+
+	public Integer getDetailNumber() {
+		return detailNumber;
+	}
+
+	public void setDetailNumber(Integer detailNumber) {
+		this.detailNumber = detailNumber;
+	}
+
+	public Double getUnitScore() {
+		return unitScore;
+	}
+
+	public void setUnitScore(Double unitScore) {
+		this.unitScore = unitScore;
+	}
+
+}

+ 49 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructInfo.java

@@ -0,0 +1,49 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.List;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class StructInfo {
+	@ApiModelProperty("是否有效")
+	private Boolean valid;
+	@ApiModelProperty("满分")
+	private Double totalScore;
+	@ApiModelProperty("难度系数")
+	private Double difficultyDegree;
+	@ApiModelProperty("题数量")
+	private List<StructQuestionInfo> structQuestionInfo;
+
+	public Double getDifficultyDegree() {
+		return difficultyDegree;
+	}
+
+	public void setDifficultyDegree(Double difficultyDegree) {
+		this.difficultyDegree = difficultyDegree;
+	}
+
+	public List<StructQuestionInfo> getStructQuestionInfo() {
+		return structQuestionInfo;
+	}
+
+	public void setStructQuestionInfo(List<StructQuestionInfo> structQuestionInfo) {
+		this.structQuestionInfo = structQuestionInfo;
+	}
+
+	public Double getTotalScore() {
+		return totalScore;
+	}
+
+	public void setTotalScore(Double totalScore) {
+		this.totalScore = totalScore;
+	}
+
+	public Boolean getValid() {
+		return valid;
+	}
+
+	public void setValid(Boolean valid) {
+		this.valid = valid;
+	}
+
+}

+ 130 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructQuestionCheckDto.java

@@ -0,0 +1,130 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailStruct;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyNumberDto;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PaperDetailUnitStructDto;
+
+public class StructQuestionCheckDto {
+	private Set<String> usedQuesIds = new HashSet<>();
+	private Boolean pub;
+	private String difficulty;
+	private PaperDetailUnitStructDto us;
+	private Integer index;
+	private Integer detailNumber;
+	private Integer needCount;
+	private StructQuestionInfo sqinfo;
+	private CoursePropertyNumberDto cp;
+	private StructQuestionCountInfo si;
+	private List<QuestionDto> questionList;
+	private PaperDetailStruct ds;
+	private Double unitScore;
+
+	public Integer getDetailNumber() {
+		return detailNumber;
+	}
+
+	public void setDetailNumber(Integer detailNumber) {
+		this.detailNumber = detailNumber;
+	}
+
+	public StructQuestionInfo getSqinfo() {
+		return sqinfo;
+	}
+
+	public void setSqinfo(StructQuestionInfo sqinfo) {
+		this.sqinfo = sqinfo;
+	}
+
+	public CoursePropertyNumberDto getCp() {
+		return cp;
+	}
+
+	public void setCp(CoursePropertyNumberDto cp) {
+		this.cp = cp;
+	}
+
+	public List<QuestionDto> getQuestionList() {
+		return questionList;
+	}
+
+	public void setQuestionList(List<QuestionDto> questionList) {
+		this.questionList = questionList;
+	}
+
+	public Integer getNeedCount() {
+		return needCount;
+	}
+
+	public void setNeedCount(Integer needCount) {
+		this.needCount = needCount;
+	}
+
+	public Boolean getPub() {
+		return pub;
+	}
+
+	public void setPub(Boolean pub) {
+		this.pub = pub;
+	}
+
+	public String getDifficulty() {
+		return difficulty;
+	}
+
+	public void setDifficulty(String difficulty) {
+		this.difficulty = difficulty;
+	}
+
+	public StructQuestionCountInfo getSi() {
+		return si;
+	}
+
+	public void setSi(StructQuestionCountInfo si) {
+		this.si = si;
+	}
+
+	public Set<String> getUsedQuesIds() {
+		return usedQuesIds;
+	}
+
+	public void setUsedQuesIds(Set<String> usedQuesIds) {
+		this.usedQuesIds = usedQuesIds;
+	}
+
+	public PaperDetailUnitStructDto getUs() {
+		return us;
+	}
+
+	public void setUs(PaperDetailUnitStructDto us) {
+		this.us = us;
+	}
+
+	public Integer getIndex() {
+		return index;
+	}
+
+	public void setIndex(Integer index) {
+		this.index = index;
+	}
+
+	public PaperDetailStruct getDs() {
+		return ds;
+	}
+
+	public void setDs(PaperDetailStruct ds) {
+		this.ds = ds;
+	}
+
+	public Double getUnitScore() {
+		return unitScore;
+	}
+
+	public void setUnitScore(Double unitScore) {
+		this.unitScore = unitScore;
+	}
+
+}

+ 60 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructQuestionCountInfo.java

@@ -0,0 +1,60 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class StructQuestionCountInfo {
+	@ApiModelProperty("数量")
+	private Integer count=0;
+	@ApiModelProperty("是否有效")
+	private Boolean valid=true;
+	@ApiModelProperty("无效信息")
+	private String invalidMsg;
+	@ApiModelProperty(hidden = true)
+	private List<RandomPaperQuestionDto> questionInfo=new ArrayList<>();
+
+	public Integer getCount() {
+		return count;
+	}
+
+	public void setCount(Integer count) {
+		this.count = count;
+	}
+
+	public Boolean getValid() {
+		return valid;
+	}
+
+	public void setValid(Boolean valid) {
+		this.valid = valid;
+	}
+
+	public String getInvalidMsg() {
+		return invalidMsg;
+	}
+
+	public void setInvalidMsg(String invalidMsg) {
+		this.invalidMsg = invalidMsg;
+	}
+
+	public StructQuestionCountInfo() {
+		super();
+	}
+
+	public StructQuestionCountInfo(Integer count, Boolean valid) {
+		super();
+		this.count = count;
+		this.valid = valid;
+	}
+
+	public List<RandomPaperQuestionDto> getQuestionInfo() {
+		return questionInfo;
+	}
+
+	public void setQuestionInfo(List<RandomPaperQuestionDto> questionInfo) {
+		this.questionInfo = questionInfo;
+	}
+
+}

+ 67 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/bean/randompaper/StructQuestionInfo.java

@@ -0,0 +1,67 @@
+package cn.com.qmth.examcloud.core.questions.service.bean.randompaper;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class StructQuestionInfo {
+	@ApiModelProperty("大题名称")
+	private String detailName;
+	@ApiModelProperty("总分")
+	private Double totalScore;
+	@ApiModelProperty("数量")
+	private Integer totalCount;
+	@ApiModelProperty("难数量")
+	private StructQuestionCountInfo hardInfo=new StructQuestionCountInfo();
+	@ApiModelProperty("中数量")
+	private StructQuestionCountInfo mediumInfo=new StructQuestionCountInfo();
+	@ApiModelProperty("易数量")
+	private StructQuestionCountInfo easyInfo=new StructQuestionCountInfo();
+
+	public String getDetailName() {
+		return detailName;
+	}
+
+	public void setDetailName(String detailName) {
+		this.detailName = detailName;
+	}
+
+	public Double getTotalScore() {
+		return totalScore;
+	}
+
+	public void setTotalScore(Double totalScore) {
+		this.totalScore = totalScore;
+	}
+
+	public Integer getTotalCount() {
+		return totalCount;
+	}
+
+	public void setTotalCount(Integer totalCount) {
+		this.totalCount = totalCount;
+	}
+
+	public StructQuestionCountInfo getHardInfo() {
+		return hardInfo;
+	}
+
+	public void setHardInfo(StructQuestionCountInfo hardInfo) {
+		this.hardInfo = hardInfo;
+	}
+
+	public StructQuestionCountInfo getMediumInfo() {
+		return mediumInfo;
+	}
+
+	public void setMediumInfo(StructQuestionCountInfo mediumInfo) {
+		this.mediumInfo = mediumInfo;
+	}
+
+	public StructQuestionCountInfo getEasyInfo() {
+		return easyInfo;
+	}
+
+	public void setEasyInfo(StructQuestionCountInfo easyInfo) {
+		this.easyInfo = easyInfo;
+	}
+
+}

+ 3 - 1
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/BasePaperCache.java

@@ -3,6 +3,7 @@ package cn.com.qmth.examcloud.core.questions.service.cache;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
 import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.bean.BasePaperCacheBean;
 import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
 import org.apache.commons.lang3.StringUtils;
@@ -11,6 +12,7 @@ import org.springframework.stereotype.Component;
 
 @Component
 public class BasePaperCache extends RandomObjectRedisCache<BasePaperCacheBean> {
+
     @Autowired
     private ExtractConfigProviderService extractConfigProviderService;
 
@@ -31,7 +33,7 @@ public class BasePaperCache extends RandomObjectRedisCache<BasePaperCacheBean> {
 
     @Override
     protected String getKeyPrefix() {
-        return "Q_PAPER:BASE_";
+        return CacheConstants.CACHE_Q_BASE_PAPER;
     }
 
     @Override

+ 0 - 6
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/Constants.java

@@ -2,12 +2,6 @@ package cn.com.qmth.examcloud.core.questions.service.cache;
 
 public interface Constants {
 
-    String CACHE_KEY_PAPER = "Q_PAPER:";
-
-    String CACHE_KEY_PAPER_FOR_DTO = "Q_PAPER:DTO_";
-
-    String CACHE_KEY_QUESTION = "Q_QUESTION:";
-
     int DEFAULT_TIME_OUT = 30 * 60;// N分钟
 
 }

+ 2 - 1
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/ExtractConfigCache.java

@@ -2,6 +2,7 @@ package cn.com.qmth.examcloud.core.questions.service.cache;
 
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigCacheBean;
 import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
 import org.apache.commons.lang3.StringUtils;
@@ -31,7 +32,7 @@ public class ExtractConfigCache extends RandomObjectRedisCache<ExtractConfigCach
 
     @Override
     protected String getKeyPrefix() {
-        return "Q_EXTRACT_CONFIG:";
+        return CacheConstants.CACHE_Q_EXTRACT_CONFIG;
     }
 
     @Override

+ 7 - 6
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/ExtractConfigPaperCache.java

@@ -1,21 +1,22 @@
 package cn.com.qmth.examcloud.core.questions.service.cache;
 
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.bean.ExtractConfigPaperCacheBean;
 import cn.com.qmth.examcloud.web.cache.HashRedisCache;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
 
 @Component
 public class ExtractConfigPaperCache extends HashRedisCache<ExtractConfigPaperCacheBean> {
+
     @Autowired
     private ExtractConfigProviderService extractConfigProviderService;
 
     @Override
-    public ExtractConfigPaperCacheBean loadFromResource(Object[] keys,Object[] subkeys) {
+    public ExtractConfigPaperCacheBean loadFromResource(Object[] keys, Object[] subkeys) {
         Long examId = (Long) subkeys[0];
         String courseCode = String.valueOf(subkeys[1]);
         String groupCode = String.valueOf(subkeys[2]);
@@ -42,7 +43,7 @@ public class ExtractConfigPaperCache extends HashRedisCache<ExtractConfigPaperCa
 
     @Override
     protected String getKeyPrefix() {
-        return "Q_PAPER:EXTRACT_CONFIG_PAPER_";
+        return CacheConstants.CACHE_Q_EXTRACT_CONFIG_PAPER;
     }
 
     @Override

+ 192 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/PaperDetailStructCache.java

@@ -0,0 +1,192 @@
+package cn.com.qmth.examcloud.core.questions.service.cache;
+
+import java.util.List;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyNumberDto;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PaperDetailUnitStructDto;
+
+public class PaperDetailStructCache{
+    private Integer number;//大题序号
+
+    private String name;//大题名称
+
+    private List<PaperDetailUnitStructDto> unitStructs;//题目类型统计
+
+    private Double totalScore;//大题总分
+
+    @SuppressWarnings("rawtypes")
+    private Map params;//大题参数
+
+    private String remark;
+
+    private Integer detailCount; //小题总数
+
+    /**
+     * 蓝图结构字段
+     */
+    private QuesStructType questionType; //题型
+
+    private Double score; //小题分数
+
+    private List<String> quesNames;//来源大题
+
+    private Integer publicSimpleCount; //公开简单总数
+
+    private Integer publicMediumCount; //公开中等总数
+
+    private Integer publicDifficultyCount; //公开困难总数
+
+    private Integer noPublicSimpleCount; //非公开简单总数
+
+    private Integer noPublicMediumCount; //非公开中等总数
+
+    private Integer noPublicDifficultyCount; //非公开困难总数  coursePropertyNumberDtos
+
+    private List<CoursePropertyNumberDto> coursePropertyNumberDtos;
+
+    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 List<PaperDetailUnitStructDto> getUnitStructs() {
+        return unitStructs;
+    }
+
+    public void setUnitStructs(List<PaperDetailUnitStructDto> unitStructs) {
+        this.unitStructs = unitStructs;
+    }
+
+    public Double getTotalScore() {
+        return totalScore;
+    }
+
+    public void setTotalScore(Double totalScore) {
+        this.totalScore = totalScore;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Map getParams() {
+        return params;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public void setParams(Map params) {
+        this.params = params;
+    }
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public Integer getDetailCount() {
+        return detailCount;
+    }
+
+    public void setDetailCount(Integer detailCount) {
+        this.detailCount = detailCount;
+    }
+
+    public PaperDetailStructCache() {
+    }
+
+    public QuesStructType getQuestionType() {
+        return questionType;
+    }
+
+    public void setQuestionType(QuesStructType questionType) {
+        this.questionType = questionType;
+    }
+
+    public Double getScore() {
+        return score;
+    }
+
+    public void setScore(Double score) {
+        this.score = score;
+    }
+
+    public List<String> getQuesNames() {
+        return quesNames;
+    }
+
+    public void setQuesNames(List<String> quesNames) {
+        this.quesNames = quesNames;
+    }
+
+    public Integer getPublicSimpleCount() {
+        return publicSimpleCount;
+    }
+
+    public void setPublicSimpleCount(Integer publicSimpleCount) {
+        this.publicSimpleCount = publicSimpleCount;
+    }
+
+    public Integer getPublicMediumCount() {
+        return publicMediumCount;
+    }
+
+    public void setPublicMediumCount(Integer publicMediumCount) {
+        this.publicMediumCount = publicMediumCount;
+    }
+
+    public Integer getPublicDifficultyCount() {
+        return publicDifficultyCount;
+    }
+
+    public void setPublicDifficultyCount(Integer publicDifficultyCount) {
+        this.publicDifficultyCount = publicDifficultyCount;
+    }
+
+    public Integer getNoPublicSimpleCount() {
+        return noPublicSimpleCount;
+    }
+
+    public void setNoPublicSimpleCount(Integer noPublicSimpleCount) {
+        this.noPublicSimpleCount = noPublicSimpleCount;
+    }
+
+    public Integer getNoPublicMediumCount() {
+        return noPublicMediumCount;
+    }
+
+    public void setNoPublicMediumCount(Integer noPublicMediumCount) {
+        this.noPublicMediumCount = noPublicMediumCount;
+    }
+
+    public Integer getNoPublicDifficultyCount() {
+        return noPublicDifficultyCount;
+    }
+
+    public void setNoPublicDifficultyCount(Integer noPublicDifficultyCount) {
+        this.noPublicDifficultyCount = noPublicDifficultyCount;
+    }
+
+    public List<CoursePropertyNumberDto> getCoursePropertyNumberDtos() {
+        return coursePropertyNumberDtos;
+    }
+
+    public void setCoursePropertyNumberDtos(
+            List<CoursePropertyNumberDto> coursePropertyNumberDtos) {
+        this.coursePropertyNumberDtos = coursePropertyNumberDtos;
+    }
+
+}

+ 216 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/PaperStructCache.java

@@ -0,0 +1,216 @@
+package cn.com.qmth.examcloud.core.questions.service.cache;
+
+import java.util.List;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.core.questions.base.enums.GenPaperType;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperStructType;
+
+
+public class PaperStructCache {
+	
+	private String id;
+
+    private String name;// 试卷结构名称
+
+    private Double totalScore;// 总分
+
+    private Integer detailCount;// 大题数量
+
+    private Integer detailUnitCount;//小题数量
+
+    @SuppressWarnings("rawtypes")
+    private Map params;
+
+    private List<PaperDetailStructCache> paperDetailStructs;// 大题
+
+    private String creator;// 创建人id
+
+    private String createTime;// 创建时间
+
+    private String orgId;// 机构ID
+
+    private Long courseId;
+    
+    private String courseNo;
+
+    private String courseName;
+
+    /**
+     * 试卷结构类型  :  精确组卷    蓝图组卷
+     */
+    private PaperStructType paperStrucType;
+
+    private String type;
+
+    /**
+     * 组卷类型     : 精细组卷      宏观组卷
+     */
+    private GenPaperType genPaperType;
+
+    private String coursePropertyId; //课程属性id
+
+    private Double difficulty; //难度
+
+    private String examRemark;//考试说明
+
+    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 getDetailCount() {
+        return detailCount;
+    }
+
+    public void setDetailCount(Integer detailCount) {
+        this.detailCount = detailCount;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public Map getParams() {
+        return params;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public void setParams(Map params) {
+        this.params = params;
+    }
+
+
+    public String getCreator() {
+        return creator;
+    }
+
+    public void setCreator(String creator) {
+        this.creator = creator;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+
+    public PaperStructCache() {
+    }
+
+    public String getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(String orgId) {
+        this.orgId = orgId;
+    }
+
+    public String getCourseNo() {
+        return courseNo;
+    }
+
+    public void setCourseNo(String courseNo) {
+        this.courseNo = courseNo;
+    }
+
+    public Integer getDetailUnitCount() {
+        return detailUnitCount;
+    }
+
+    public void setDetailUnitCount(Integer detailUnitCount) {
+        this.detailUnitCount = detailUnitCount;
+    }
+
+    public GenPaperType getGenPaperType() {
+        return genPaperType;
+    }
+
+    public void setGenPaperType(GenPaperType genPaperType) {
+        this.genPaperType = genPaperType;
+    }
+
+    public PaperStructType getPaperStrucType() {
+        return paperStrucType;
+    }
+
+    public void setPaperStrucType(PaperStructType paperStrucType) {
+        this.paperStrucType = paperStrucType;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getCoursePropertyId() {
+        return coursePropertyId;
+    }
+
+    public void setCoursePropertyId(String coursePropertyId) {
+        this.coursePropertyId = coursePropertyId;
+    }
+
+    public Double getDifficulty() {
+        return difficulty;
+    }
+
+    public void setDifficulty(Double difficulty) {
+        this.difficulty = difficulty;
+    }
+
+    public String getExamRemark() {
+        return examRemark;
+    }
+
+    public void setExamRemark(String examRemark) {
+        this.examRemark = examRemark;
+    }
+
+    public String getCourseName() {
+        return courseName;
+    }
+
+    public void setCourseName(String courseName) {
+        this.courseName = courseName;
+    }
+
+	public Long getCourseId() {
+		return courseId;
+	}
+
+	public void setCourseId(Long courseId) {
+		this.courseId = courseId;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+	public List<PaperDetailStructCache> getPaperDetailStructs() {
+		return paperDetailStructs;
+	}
+
+	public void setPaperDetailStructs(List<PaperDetailStructCache> paperDetailStructs) {
+		this.paperDetailStructs = paperDetailStructs;
+	}
+
+    
+}

+ 3 - 1
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/QuestionAnswerCache.java

@@ -3,6 +3,7 @@ package cn.com.qmth.examcloud.core.questions.service.cache;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.service.QuesService;
 import cn.com.qmth.examcloud.core.questions.service.bean.QuestionAnswerBean;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.bean.QuestionAnswerCacheBean;
 import cn.com.qmth.examcloud.web.cache.RandomObjectRedisCache;
 import org.apache.commons.lang3.StringUtils;
@@ -11,6 +12,7 @@ import org.springframework.stereotype.Component;
 
 @Component
 public class QuestionAnswerCache extends RandomObjectRedisCache<QuestionAnswerCacheBean> {
+
     @Autowired
     private QuesService quesService;
 
@@ -33,7 +35,7 @@ public class QuestionAnswerCache extends RandomObjectRedisCache<QuestionAnswerCa
 
     @Override
     protected String getKeyPrefix() {
-        return "Q_QUESTION:ANSWER_";
+        return CacheConstants.CACHE_Q_QUESTION_ANSWER;
     }
 
     @Override

+ 7 - 6
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/QuestionCache.java

@@ -1,22 +1,23 @@
 package cn.com.qmth.examcloud.core.questions.service.cache;
 
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.service.ExtractConfigProviderService;
 import cn.com.qmth.examcloud.question.commons.core.question.DefaultQuestion;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.bean.QuestionCacheBean;
 import cn.com.qmth.examcloud.web.cache.HashRedisCache;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
 
 @Component
 public class QuestionCache extends HashRedisCache<QuestionCacheBean> {
+
     @Autowired
     private ExtractConfigProviderService extractConfigProviderService;
 
     @Override
-    public QuestionCacheBean loadFromResource(Object[] keys,Object[] subkeys) {
+    public QuestionCacheBean loadFromResource(Object[] keys, Object[] subkeys) {
         Long examId = (Long) subkeys[0];
         String courseCode = String.valueOf(subkeys[1]);
         String groupCode = String.valueOf(subkeys[2]);
@@ -47,7 +48,7 @@ public class QuestionCache extends HashRedisCache<QuestionCacheBean> {
 
     @Override
     protected String getKeyPrefix() {
-        return "Q_QUESTION:";
+        return CacheConstants.CACHE_Q_QUESTION;
     }
 
     @Override

+ 44 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/cache/RandomPaperCache.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.questions.service.cache;
+
+import java.util.List;
+import java.util.Map;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.RandomPaperQuestion;
+
+public class RandomPaperCache {
+	private String name;
+
+	private String paperStructId;
+
+	/**
+	 * key描述 精确结构:大题号-结构序号(1开始)-公开/非公开-难度 | 示例:1-1-true-难
+	 * 蓝图结构:大题号-一级属性id-二级属性id-公开/非公开-难度 |
+	 * 示例:1-1-60efd3e85d030a52bb08b1e8-60efd3e85d030a52bb08b1e9-true-易
+	 */
+	private Map<String, List<RandomPaperQuestion>> questionMap;
+
+	public String getPaperStructId() {
+		return paperStructId;
+	}
+
+	public void setPaperStructId(String paperStructId) {
+		this.paperStructId = paperStructId;
+	}
+
+	public Map<String, List<RandomPaperQuestion>> getQuestionMap() {
+		return questionMap;
+	}
+
+	public void setQuestionMap(Map<String, List<RandomPaperQuestion>> questionMap) {
+		this.questionMap = questionMap;
+	}
+
+	public String getName() {
+		return name;
+	}
+
+	public void setName(String name) {
+		this.name = name;
+	}
+
+}

+ 2 - 2
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExportPaperServiceImpl.java

@@ -564,7 +564,8 @@ public class ExportPaperServiceImpl implements ExportPaperService {
      * @param computerTestPaper
      * @param jsonDirectoryPath
      */
-    private void makeComputerTestPaperToJsonFile(String courseCode, ComputerTestPaper computerTestPaper,
+    @SuppressWarnings("deprecation")
+	private void makeComputerTestPaperToJsonFile(String courseCode, ComputerTestPaper computerTestPaper,
             String jsonDirectoryPath) {
         // 创建新的JSON文件
         File file = new File(jsonDirectoryPath + File.separator + courseCode + ".json");
@@ -589,7 +590,6 @@ public class ExportPaperServiceImpl implements ExportPaperService {
         }
     }
 
-    @SuppressWarnings("unused")
     @Override
     public void exportPaperFiles(List<String> paperIds, String exportContentList,
             HttpServletResponse response, PaperSeqMode seqMode, String examType, User user) throws Exception {

+ 230 - 8
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExportThemisPaperServiceImpl.java

@@ -92,7 +92,8 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 							if (blocks != null && blocks.size() > 0) {
 								for (ThemisBlock block : blocks) {
 									if (block.getType().equals("audio")) {
-										String id = block.getValue().substring(0, block.getValue().lastIndexOf("."));
+										String val=block.getValue().toString();
+										String id = val.substring(0, val.lastIndexOf("."));
 										QuestionAudio questionAudio = Model.of(questionAudioRepo.findById(id));
 										String audioFileName = questionAudio.getId() + "."
 												+ questionAudio.getFileSuffixes();
@@ -120,7 +121,8 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 										if (blocks != null && blocks.size() > 0) {
 											for (ThemisBlock block : blocks) {
 												if (block.getType().equals("audio")) {
-													String id = block.getValue().substring(0, block.getValue().lastIndexOf("."));
+													String val=block.getValue().toString();
+													String id = val.substring(0, val.lastIndexOf("."));
 													QuestionAudio questionAudio = Model
 															.of(questionAudioRepo.findById(id));
 													String audioFileName = questionAudio.getId() + "."
@@ -153,6 +155,7 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		makePaperToJsonFile(pa.getPaper(), jsonDirectoryPath);
 	}
 
+	@SuppressWarnings("deprecation")
 	private void makeAnswerToJsonFile(ThemisAnswer pa, String jsonDirectoryPath) {
 		// 创建新的JSON文件
 		File file = new File(jsonDirectoryPath + File.separator + "answer.json");
@@ -165,7 +168,7 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		FileOutputStream outputStream = null;
 		try {
 			outputStream = new FileOutputStream(file);
-			byte b[] = strJSON.getBytes();
+			byte b[] = strJSON.getBytes("UTF-8");
 			outputStream.write(b);
 			outputStream.flush();
 		} catch (FileNotFoundException e) {
@@ -177,6 +180,7 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		}
 	}
 
+	@SuppressWarnings("deprecation")
 	private void makePaperToJsonFile(ThemisPaper pa, String jsonDirectoryPath) {
 		// 创建新的JSON文件
 		File file = new File(jsonDirectoryPath + File.separator + "paper.json");
@@ -189,7 +193,7 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		FileOutputStream outputStream = null;
 		try {
 			outputStream = new FileOutputStream(file);
-			byte b[] = strJSON.getBytes();
+			byte b[] = strJSON.getBytes("UTF-8");
 			outputStream.write(b);
 			outputStream.flush();
 		} catch (FileNotFoundException e) {
@@ -227,7 +231,13 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 				computerTestQuestion.setNumber(i + 1);
 				themisAnswerContent.setNumber(i + 1);
 				// 得到小题题干
-				computerTestQuestion.setBody(getBody(paperDetailUnit.getQuestion().getQuesBody(), computerTestPaper));
+				if(computerTestQuestion.getStructType()==4) {
+					computerTestQuestion.setBody(getFillBlankBody(paperDetailUnit.getQuestion().getQuesBody(), computerTestPaper));
+				}else if(computerTestQuestion.getStructType()==6){
+					computerTestQuestion.setBody(getNestedBody(paperDetailUnit.getQuestion().getQuesBody(), computerTestPaper));
+				}else {
+					computerTestQuestion.setBody(getBody(paperDetailUnit.getQuestion().getQuesBody(), computerTestPaper));
+				}
 				// 得到小题所有选项
 				computerTestQuestion.setOptions(getOption(paperDetailUnit.getQuestion(), computerTestPaper));
 				// 得到小题的答案
@@ -256,7 +266,14 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 						// 设置套题中小题题号
 						subcomputerTestQuestion.setNumber(j + 1);
 						subthemisAnswerContent.setNumber(j + 1);
-						subcomputerTestQuestion.setBody(getBody(subQuestion.getQuesBody(), computerTestPaper));
+						
+						if(subcomputerTestQuestion.getStructType()==4) {
+							subcomputerTestQuestion.setBody(getFillBlankBody(subQuestion.getQuesBody(), computerTestPaper));
+						}else if(computerTestQuestion.getStructType()==6){
+							subcomputerTestQuestion.setBody(getNestedBody(subQuestion.getQuesBody(), computerTestPaper));
+						}else {
+							subcomputerTestQuestion.setBody(getBody(subQuestion.getQuesBody(), computerTestPaper));
+						}
 						subcomputerTestQuestion.setOptions(getOption(subQuestion, computerTestPaper));
 						if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
 								|| subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
@@ -293,7 +310,6 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		ret.setAnswer(themisAnswer);
 		return ret;
 	}
-
 	private ThemisSections getBody(String str, ThemisPaper computerTestPaper) {
 		ThemisSections body = new ThemisSections();
 		List<ThemisSection> sections = new ArrayList<>();
@@ -314,7 +330,27 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		body.setSections(sections);
 		return body;
 	}
-
+	private ThemisSections getFillBlankBody(String str, ThemisPaper computerTestPaper) {
+		ThemisSections body = new ThemisSections();
+		List<ThemisSection> sections = new ArrayList<>();
+		// 得到小题题干或者答案行数
+		if (StringUtils.isBlank(str)) {
+			return body;
+		}
+		String[] questionRowStrings = str.split("</p>");
+		for (int i = 0; i < questionRowStrings.length; i++) {
+			List<ThemisBlock> blocks = disposeFillBlankQuestionBody(questionRowStrings[i], computerTestPaper);
+			if (blocks != null && blocks.size() > 0) {
+				ThemisSection section = new ThemisSection();
+				// 将小题题干拆分为Block集合
+				section.setBlocks(blocks);
+				sections.add(section);
+			}
+		}
+		body.setSections(sections);
+		return body;
+	}
+	
 	private List<ThemisSubjectiveAnswer> getAnswer(String str, ThemisPaper computerTestPaper) {
 		List<ThemisSection> sections = new ArrayList<>();
 		// 得到小题题干或者答案行数
@@ -338,6 +374,192 @@ public class ExportThemisPaperServiceImpl implements ExportThemisPaperService {
 		an.setSections(sections);
 		return ans;
 	}
+	private String changeCloze(String text) {
+		StringBuffer buffer = new StringBuffer();
+        String regex = "##(\\d)##";
+
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(text);
+        // 使用find()方法查找匹配项
+        while (matcher.find()) {
+        	matcher.appendReplacement(buffer, "__"+matcher.group(1)+"__");
+        }
+        return buffer.toString();
+	}
+	private ThemisSections getNestedBody(String str, ThemisPaper computerTestPaper) {
+		ThemisSections body = new ThemisSections();
+		List<ThemisSection> sections = new ArrayList<>();
+		// 得到小题题干或者答案行数
+		if (StringUtils.isBlank(str)) {
+			return body;
+		}
+		String[] questionRowStrings = str.split("</p>");
+		for (int i = 0; i < questionRowStrings.length; i++) {
+			List<ThemisBlock> blocks = disposeNestedQuestionBody(questionRowStrings[i], computerTestPaper);
+			if (blocks != null && blocks.size() > 0) {
+				ThemisSection section = new ThemisSection();
+				// 将小题题干拆分为Block集合
+				section.setBlocks(blocks);
+				sections.add(section);
+			}
+		}
+		body.setSections(sections);
+		return body;
+	}
+	private List<ThemisBlock> disposeNestedQuestionBody(String questionRow, ThemisPaper computerTestPaper) {
+		List<ThemisBlock> blocks = new ArrayList<>();
+		// 去掉每行里面的<p>,<span>,</span>标签
+		questionRow = questionRow.replaceAll("<p>", "").replaceAll("</p>", "").replaceAll("<span>", "")
+				.replaceAll("</span>", "").replaceAll("</a>", "");
+		String[] questionRowStrings = questionRow.split("<|/>|>");
+		boolean hasAudio = false;
+		for (int i = 0; i < questionRowStrings.length; i++) {
+			ThemisBlock block = new ThemisBlock();
+			String rowStr = questionRowStrings[i];
+			// 判断是否有图片
+			if (rowStr.startsWith("img")) {
+				rowStr = "<" + rowStr + ">";
+				Map<String, Object> param = new HashMap<>();
+				// 需要继续做截取,取到Parma
+				block.setType("image");
+				// 获取图片的路径
+				List<String> strSrcList = getImg(rowStr, "src");
+				if (strSrcList.size() > 0) {
+					String strSrc = strSrcList.get(0).replaceAll("src=\"", "").replaceAll("\"", "");
+					block.setValue(strSrc);
+				}
+				// 获取图片的高度
+				List<String> strHeightList = getImg(rowStr, "height");
+				if (strHeightList.size() > 0) {
+					String strHeight = strHeightList.get(0).replaceAll("height=\"", "").replaceAll("\"", "");
+					param.put("height", strHeight);
+				}
+				// 获取图片的宽度
+				List<String> strWidthList = getImg(rowStr, "width");
+				if (strHeightList.size() > 0) {
+					String strWidth = strWidthList.get(0).replaceAll("width=\"", "").replaceAll("\"", "");
+					param.put("width", strWidth);
+				}
+				block.setParam(param);
+				blocks.add(block);
+			} else if (rowStr.startsWith("a") && rowStr.contains("id") && rowStr.contains("name")) { // 处理音频
+				rowStr = "<" + rowStr + ">";
+				block.setType("audio");
+				String id=CommonUtils.getAttrValue(rowStr, "id");
+				QuestionAudio questionAudio = Model.of(questionAudioRepo.findById(id));
+				block.setValue(questionAudio.getId() + "."+ questionAudio.getFileSuffixes());
+				blocks.add(block);
+				hasAudio = true;
+			} else {
+				block.setType("text");
+				rowStr = rowStr.replace("&nbsp;", "");// 消除空格
+				rowStr = rowStr.replace("&quot;", "\"");// 将&quot;转换成\"
+				rowStr = rowStr.replace("&lt;", "<");// 将&lt;转换成<
+				rowStr = rowStr.replace("&gt;", ">");// 将&gt;转换成>
+				rowStr = rowStr.replace("&amp;", "&");// 将&amp;转换成&
+				if (StringUtils.isNotBlank(rowStr)) {
+					block.setValue(changeCloze(rowStr));
+					blocks.add(block);
+				}
+			}
+		}
+		if (hasAudio) {
+			computerTestPaper.setHasAudio(hasAudio);
+		}
+		return blocks;
+	}
+	
+	private List<ThemisBlock> disposeFillBlankQuestionBody(String questionRow, ThemisPaper computerTestPaper) {
+		List<ThemisBlock> blocks = new ArrayList<>();
+		// 去掉每行里面的<p>,<span>,</span>标签
+		questionRow = questionRow.replaceAll("<p>", "").replaceAll("</p>", "").replaceAll("<span>", "")
+				.replaceAll("</span>", "").replaceAll("</a>", "");
+		String[] questionRowStrings = questionRow.split("<|/>|>");
+		boolean hasAudio = false;
+		int blankIndex=0;
+		for (int i = 0; i < questionRowStrings.length; i++) {
+			ThemisBlock block = new ThemisBlock();
+			String rowStr = questionRowStrings[i];
+			// 判断是否有图片
+			if (rowStr.startsWith("img")) {
+				rowStr = "<" + rowStr + ">";
+				Map<String, Object> param = new HashMap<>();
+				// 需要继续做截取,取到Parma
+				block.setType("image");
+				// 获取图片的路径
+				List<String> strSrcList = getImg(rowStr, "src");
+				if (strSrcList.size() > 0) {
+					String strSrc = strSrcList.get(0).replaceAll("src=\"", "").replaceAll("\"", "");
+					block.setValue(strSrc);
+				}
+				// 获取图片的高度
+				List<String> strHeightList = getImg(rowStr, "height");
+				if (strHeightList.size() > 0) {
+					String strHeight = strHeightList.get(0).replaceAll("height=\"", "").replaceAll("\"", "");
+					param.put("height", strHeight);
+				}
+				// 获取图片的宽度
+				List<String> strWidthList = getImg(rowStr, "width");
+				if (strHeightList.size() > 0) {
+					String strWidth = strWidthList.get(0).replaceAll("width=\"", "").replaceAll("\"", "");
+					param.put("width", strWidth);
+				}
+				block.setParam(param);
+				blocks.add(block);
+			} else if (rowStr.startsWith("a") && rowStr.contains("id") && rowStr.contains("name")) { // 处理音频
+				rowStr = "<" + rowStr + ">";
+				block.setType("audio");
+				String id=CommonUtils.getAttrValue(rowStr, "id");
+				QuestionAudio questionAudio = Model.of(questionAudioRepo.findById(id));
+				block.setValue(questionAudio.getId() + "."+ questionAudio.getFileSuffixes());
+				blocks.add(block);
+				hasAudio = true;
+			} else {
+				rowStr = rowStr.replace("&nbsp;", "");// 消除空格
+				rowStr = rowStr.replace("&quot;", "\"");// 将&quot;转换成\"
+				rowStr = rowStr.replace("&lt;", "<");// 将&lt;转换成<
+				rowStr = rowStr.replace("&gt;", ">");// 将&gt;转换成>
+				rowStr = rowStr.replace("&amp;", "&");// 将&amp;转换成&
+		        
+				String regex = "###";
+
+		        Pattern pattern = Pattern.compile(regex);
+		        Matcher matcher = pattern.matcher(rowStr);
+		        int indexStart=0;
+		        // 使用find()方法查找匹配项
+		        while (matcher.find()) {
+		        	blankIndex++;
+		            String content=rowStr.substring(indexStart, matcher.start());
+		            if(StringUtils.isNotEmpty(content)) {
+		            	ThemisBlock temblock = new ThemisBlock();
+			            temblock.setType("text");
+			            temblock.setValue(content);
+						blocks.add(temblock);
+		            }
+		            ThemisBlock blankblock = new ThemisBlock();
+		            blankblock.setType("cloze");
+		            blankblock.setValue(blankIndex);
+		            blocks.add(blankblock);
+		            indexStart=matcher.start()+regex.length();
+		        }
+		        if(indexStart<rowStr.length()) {
+		        	// 输出最后一个分隔符之后的字符串
+			        String content=rowStr.substring(indexStart, rowStr.length());
+		            if(StringUtils.isNotEmpty(content)) {
+		            	ThemisBlock temblock = new ThemisBlock();
+			            temblock.setType("text");
+			            temblock.setValue(content);
+						blocks.add(temblock);
+		            }
+		        }
+			}
+		}
+		if (hasAudio) {
+			computerTestPaper.setHasAudio(hasAudio);
+		}
+		return blocks;
+	}
+	
 
 	private List<ThemisBlock> disposeQuestionBodyOrOption(String questionRow, ThemisPaper computerTestPaper) {
 		List<ThemisBlock> blocks = new ArrayList<>();

+ 643 - 615
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExtractConfigProviderServiceImpl.java

@@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.Example;
 import org.springframework.stereotype.Service;
 
+import cn.com.qmth.examcloud.api.commons.enums.CallType;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
 import cn.com.qmth.examcloud.core.questions.base.Model;
@@ -69,620 +70,647 @@ import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 @Service("extractConfigCloudService")
 public class ExtractConfigProviderServiceImpl implements ExtractConfigProviderService {
 
-    private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigProviderServiceImpl.class);
-
-    @Autowired
-    private ExtractConfigService extractConfigService;
-
-    @Autowired
-    private PaperRepo paperRepo;
-
-    @Autowired
-    private PaperDetailRepo paperDetailRepo;
-
-    @Autowired
-    private PaperDetailUnitRepo paperDetailUnitRepo;
-
-    @Autowired
-    private QuesRepo quesRepo;
-
-    @Autowired
-    private AudioTimeConfigRepo audioTimeConfigRepo;
-
-    @Autowired
-    private QuestionAudioService questionAudioService;
-
-    @Autowired
-    private QuesService quesService;
-
-    @Autowired
-    private PaperService paperService;
-
-//    @Autowired
-//    private SysProperty sysProperty;
-
-    @Override
-    public ExtractConfigCacheBean getExtractConfig(Long examId, String courseCode) {
-        ExtractConfig extractConfig = extractConfigService.findConfig(new ExtractConfig(examId, courseCode));
-        if (extractConfig == null) {
-            throw new StatusException("500", "该课程调卷规则未制定,请联系学校!");
-        }
-
-        List<ExamPaper> examPapers = extractConfig.getExamPaperList();
-        if (CollectionUtils.isEmpty(examPapers)) {
-            throw new StatusException("500", "可供抽取的试卷集合为空!");
-        }
-
-        ExtractConfigCacheBean cacheBean = new ExtractConfigCacheBean();
-        cacheBean.setId(extractConfig.getId());
-        cacheBean.setExamId(examId);
-        cacheBean.setCourseCode(courseCode);
-        cacheBean.setSortQuestionOrder(extractConfig.getScrambling_the_question_order() != 0);
-        cacheBean.setSortOptionOrder(extractConfig.getScrambling_the_option_order() != 0);
-
-        List<ExtractConfigDetailCacheBean> details = new ArrayList<>();
-        for (ExamPaper examPaper : examPapers) {
-            details.add(new ExtractConfigDetailCacheBean(examPaper.getGroupCode(), examPaper.getPaper().getId(), examPaper.getWeight()));
-        }
-        cacheBean.setDetails(details);
-
-        return cacheBean;
-    }
-
-    @Override
-    public ExtractConfigPaperCacheBean getExtractConfigPaper(Long examId, String courseCode, String groupCode, String paperId) {
-        DefaultPaper defaultPaper = this.buildDefaultByBasePaper(paperId, examId, courseCode, groupCode);
-
-        ExtractConfigPaperCacheBean cacheBean = new ExtractConfigPaperCacheBean();
-        cacheBean.setDefaultPaper(defaultPaper);
-        return cacheBean;
-    }
-
-    @Override
-    public ExtractConfigPaper getDefaultPaper(Long examId, String courseCode, String groupCode) {
-        LOG.info("调卷开始...");
-
-        ExtractConfigCacheBean extractConfigCache = CacheHelper.getExtractConfig(examId, courseCode);
-        if (extractConfigCache == null) {
-            throw new StatusException("500", "该课程调卷规则未制定,请联系学校!");
-        }
-
-        final String msg = "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则!";
-        Map<String, String> paperTypeMaps = this.getExamPaperByProbability(extractConfigCache.getDetails());
-        if (paperTypeMaps.isEmpty()) {
-            throw new StatusException("500", msg);
-        }
-
-        String basePaperId = paperTypeMaps.get(groupCode);
-        if (StringUtils.isEmpty(basePaperId)) {
-            throw new StatusException("500", msg);
-        }
-
-        LOG.info("构建试卷结构..." + basePaperId);
-        ExtractConfigPaperCacheBean extractConfigPaperCache = CacheHelper.getExtractConfigPaper(examId, courseCode, groupCode, basePaperId);
-        if (extractConfigPaperCache == null) {
-            throw new StatusException("500", msg);
-        }
-
-        ExtractConfigPaper result = new ExtractConfigPaper();
-        result.setDefaultPaper(extractConfigPaperCache.getDefaultPaper());
-        result.setPaperId(basePaperId);
-        result.setSortQuestionOrder(extractConfigCache.getSortQuestionOrder());
-        result.setSortOptionOrder(extractConfigCache.getSortOptionOrder());
-        return result;
-    }
-
-    /**
-     * 每个试卷类型取出一套试卷
-     * {A:paperId,B:paperId} A是试卷类型,paperId是A类型下选定的试卷ID
-     */
-    private Map<String, String> getExamPaperByProbability(List<ExtractConfigDetailCacheBean> examPapers) {
-        if (CollectionUtils.isEmpty(examPapers)) {
-            throw new StatusException("500", "可供抽取的试卷集合为空!");
-        }
-
-        Map<String, List<ExtractConfigDetailCacheBean>> examPaperMaps = new HashMap<>();
-        for (ExtractConfigDetailCacheBean examPaper : examPapers) {
-            if (examPaperMaps.containsKey(examPaper.getGroupCode())) {
-                examPaperMaps.get(examPaper.getGroupCode()).add(examPaper);
-            } else {
-                List<ExtractConfigDetailCacheBean> list = new ArrayList<>();
-                list.add(examPaper);
-                examPaperMaps.put(examPaper.getGroupCode(), list);
-            }
-        }
-
-        Map<String, String> paperTypeMaps = new HashMap<>();
-        for (Map.Entry<String, List<ExtractConfigDetailCacheBean>> entry : examPaperMaps.entrySet()) {
-            List<ExtractConfigDetailCacheBean> list = examPaperMaps.get(entry.getKey());
-
-            String paperId = this.getPaperByProbability(list);
-            if (StringUtils.isEmpty(paperId)) {
-                continue;
-            }
-
-            paperTypeMaps.put(entry.getKey(), paperId);
-        }
-
-        return paperTypeMaps;
-    }
-
-    /**
-     * 根据设定几率取出一套试卷
-     */
-    private String getPaperByProbability(List<ExtractConfigDetailCacheBean> examPapers) {
-        int sum = 0;
-        for (ExtractConfigDetailCacheBean examPaper : examPapers) {
-            sum += examPaper.getWeight();
-        }
-
-        // 从1开始  
-        int r = new Random().nextInt(sum) + 1;
-        for (ExtractConfigDetailCacheBean examPaper : examPapers) {
-            r -= examPaper.getWeight();
-            if (r <= 0) {
-                return examPaper.getPaperId();// 选中
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * 根据paper对象构建DefaultPaper对象
-     */
-    public DefaultPaper buildDefaultByBasePaper(String basePaperId, Long examId, String courseCode, String groupCode) {
-        Paper basePaper = Model.of(paperRepo.findById(basePaperId));
-        return this.buildDefaultByBasePaper(basePaper, examId, courseCode, groupCode);
-    }
-
-    /**
-     * 根据paper对象构建DefaultPaper对象
-     */
-    public DefaultPaper buildDefaultByBasePaper(Paper basePaper, Long examId, String courseCode, String groupCode) {
-        LOG.debug("开始包装网考需要的试卷结构...");
-
-        //获取大题
-        List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(basePaper);
-
-        //排序大题
-        Collections.sort(paperDetails);
-
-        //将小题全部取出来,只取一次,减少对数据库的查询
-        List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaper(basePaper);
-        boolean fullyObjective = checkIsAllQbjectiveQuestion(allPaperDetailUnits);
-
-        //根据大题id将小题归类
-        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream().collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
-
-        LOG.debug("循环大题,开始组装对象...");
-
-        //生成新的分组集合
-        List<DefaultQuestionGroup> questionGroupList = new ArrayList<>();
-        for (int i = 0; i < paperDetails.size(); i++) {
-            PaperDetail paperDetail = paperDetails.get(i);
-            DefaultQuestionGroup defaultQuestionGroup = new DefaultQuestionGroup();
-            defaultQuestionGroup.setGroupName(paperDetail.getName());
-            defaultQuestionGroup.setGroupScore(paperDetail.getScore());
-
-            //获取原小题的集合
-            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
-            if (CollectionUtils.isEmpty(paperDetailUnits)) {
-                LOG.warn("试卷大题下面没有小题!");
-                throw new StatusException("500", "考试试卷有误,请联系老师!");
-            }
-
-            //按题号顺序排序
-            Collections.sort(paperDetailUnits);
-
-            //生成新的题包装器集合
-            List<DefaultQuestionStructureWrapper> questionWrapperList = new ArrayList<>();
-            for (int j = 0; j < paperDetailUnits.size(); j++) {
-                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
-                DefaultQuestionStructureWrapper defaultQuestionStructureWrapper = new DefaultQuestionStructureWrapper();
-                defaultQuestionStructureWrapper.setQuestionId(paperDetailUnit.getQuestion().getId());
-                defaultQuestionStructureWrapper.setVersion(CommonUtils.QUESTION_VERSION);
-                defaultQuestionStructureWrapper.setQuestionScore(paperDetailUnit.getScore());
-                defaultQuestionStructureWrapper.setTimeLimit(paperDetailUnit.getTimeLimit());
-
-                if (examId != null) {
-                    //设置音频播放次数
-                    if (paperDetailUnit.getQuestion().getHasAudio() != null && paperDetailUnit.getQuestion().getHasAudio()) {
-                        AudioTimeConfig find = new AudioTimeConfig(examId.toString(), courseCode, groupCode, paperDetailUnit.getQuestion().getId());
-                        AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo.findOne(Example.of(find)));
-                        if (audioTimeConfig != null) {
-                            defaultQuestionStructureWrapper.setLimitedPlayTimes(audioTimeConfig.getPlayTime());
-                        }
-                    }
-                }
-
-                //生成新的题单元包装器
-                List<DefaultQuestionUnitWrapper> defaultQuestionUnitWrappers = new ArrayList<>();
-                if (paperDetailUnit.getQuestion().getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                    List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
-                    List<Double> scoreList = paperDetailUnit.getSubScoreList();
-                    if (subQuesList != null && subQuesList.size() > 0) {
-                        for (int k = 0; k < subQuesList.size(); k++) {
-                            DefaultQuestionUnitWrapper defaultQuestionUnitWrapper = buildQuesUnitWrapper(subQuesList.get(k), scoreList.get(k));
-                            defaultQuestionUnitWrappers.add(defaultQuestionUnitWrapper);
-                        }
-                    }
-                } else {
-                    DefaultQuestionUnitWrapper defaultQuestionUnitWrapper = buildQuesUnitWrapper(paperDetailUnit.getQuestion(), paperDetailUnit.getScore());
-                    defaultQuestionUnitWrappers.add(defaultQuestionUnitWrapper);
-                }
-
-                defaultQuestionStructureWrapper.setQuestionUnitWrapperList(defaultQuestionUnitWrappers);
-                questionWrapperList.add(defaultQuestionStructureWrapper);
-            }
-
-            defaultQuestionGroup.setQuestionWrapperList(questionWrapperList);
-            questionGroupList.add(defaultQuestionGroup);
-        }
-
-        DefaultPaper defaultPaper = new DefaultPaper();
-        defaultPaper.setName(basePaper.getName());
-        defaultPaper.setQuestionGroupList(questionGroupList);
-        defaultPaper.setFullyObjective(fullyObjective);
-
-        return defaultPaper;
-    }
-
-    /**
-     * 根据question生成题单元包装器
-     *
-     * @param question
-     * @param score
-     * @return
-     */
-    private DefaultQuestionUnitWrapper buildQuesUnitWrapper(Question question, Double score) {
-        Integer[] optionPermutation = null;
-        if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-            int length = question.getQuesOptions().size();
-            optionPermutation = new Integer[length];
-
-            for (int i = 0; i < length; i++) {
-                optionPermutation[i] = i;
-            }
-        }
-
-        DefaultQuestionUnitWrapper defaultQuestionUnitWrapper = new DefaultQuestionUnitWrapper();
-        defaultQuestionUnitWrapper.setOptionPermutation(optionPermutation);
-        defaultQuestionUnitWrapper.setQuestionScore(score);
-        defaultQuestionUnitWrapper.setQuestionType(getByOldType(question.getQuestionType()));
-        defaultQuestionUnitWrapper.setAnswerType(question.getAnswerType());//作答类型
-        return defaultQuestionUnitWrapper;
-    }
-
-    /**
-     * 题型转换方法
-     *
-     * @param quesStructType
-     * @return
-     */
-    private QuestionType getByOldType(QuesStructType quesStructType) {
-        if (quesStructType == QuesStructType.BOOL_ANSWER_QUESTION) {
-            return QuestionType.TRUE_OR_FALSE;
-        }
-        if (quesStructType == QuesStructType.FILL_BLANK_QUESTION) {
-            return QuestionType.FILL_UP;
-        }
-        if (quesStructType == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-            return QuestionType.MULTIPLE_CHOICE;
-        }
-        if (quesStructType == QuesStructType.SINGLE_ANSWER_QUESTION) {
-            return QuestionType.SINGLE_CHOICE;
-        }
-        if (quesStructType == QuesStructType.TEXT_ANSWER_QUESTION) {
-            return QuestionType.ESSAY;
-        }
-        return null;
-    }
-
-    @Override
-    public DefaultQuestion getDefaultQuestion(Long examId, String courseCode, String groupCode, String questionId) {
-        LOG.debug("网考根据调卷规则中试题id:" + questionId + "获取单个试题...");
-        long beginTime = System.currentTimeMillis();
-
-        Question question = Model.of(quesRepo.findById(questionId));
-        if (question == null) {
-            throw new StatusException("500", "试题不存在!");
-        }
-
-        quesService.formatQues(question);
-        LOG.debug("查询单个试题耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
-
-        //封装成新的题单元集合
-        beginTime = System.currentTimeMillis();
-        DefaultQuestionStructure defaultQuestionStructure = new DefaultQuestionStructure();
-        if (question.getHasAudio() == null || false == question.getHasAudio()) {
-            defaultQuestionStructure.setHasAudios(false);
-        } else {
-            defaultQuestionStructure.setHasAudios(true);
-        }
-        defaultQuestionStructure.setVersion(CommonUtils.QUESTION_VERSION);
-
-        List<DefaultQuestionUnit> questionUnitList = new ArrayList<>();
-        if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-            defaultQuestionStructure.setBody(question.getQuesBody());
-
-            //获取套题下面所有子题
-            List<Question> subQuesList = question.getSubQuestions();
-            if (CollectionUtils.isNotEmpty(subQuesList)) {
-                for (int i = 0; i < subQuesList.size(); i++) {
-                    Question subQuestion = subQuesList.get(i);
-                    DefaultQuestionUnit defaultQuestionUnit = buildQuestionUnit(subQuestion);
-                    questionUnitList.add(defaultQuestionUnit);
-                }
-            }
-        } else {
-            DefaultQuestionUnit defaultQuestionUnit = buildQuestionUnit(question);
-            questionUnitList.add(defaultQuestionUnit);
-        }
-
-        defaultQuestionStructure.setQuestionUnitList(questionUnitList);
-        DefaultQuestion defaultQuestion = new DefaultQuestion();
-        defaultQuestion.setId(questionId);
-        defaultQuestion.setMasterVersion(defaultQuestionStructure);
-
-        if (examId != null) {
-            LOG.debug("替换试题单元中的音频路径...");
-            appendAudioFlag(defaultQuestion, String.valueOf(examId), courseCode, groupCode, question);
-        }
-
-        LOG.debug("封装成新的题单元集合耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
-        return defaultQuestion;
-    }
-
-    /**
-     * 构建试题单元
-     *
-     * @param question
-     * @return
-     */
-    private DefaultQuestionUnit buildQuestionUnit(Question question) {
-        DefaultQuestionUnit defaultQuestionUnit = new DefaultQuestionUnit();
-        defaultQuestionUnit.setAnswerType(question.getAnswerType());//作答类型
-        defaultQuestionUnit.setBody(question.getQuesBody());
-        defaultQuestionUnit.setQuestionType(getByOldType(question.getQuestionType()));
-
-        //如果是单选或者多选,添加选项和答案转换
-        if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-                || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-            List<DefaultQuestionOption> defaultQuestionOptions = new ArrayList<>();
-            List<QuesOption> quesOptions = question.getQuesOptions();
-
-            if (quesOptions != null && quesOptions.size() > 0) {
-                for (int i = 0; i < quesOptions.size(); i++) {
-                    QuesOption quesOption = quesOptions.get(i);
-                    DefaultQuestionOption defaultQuestionOption = new DefaultQuestionOption();
-                    defaultQuestionOption.setBody(quesOption.getOptionBody());
-                    defaultQuestionOptions.add(defaultQuestionOption);
-                }
-            }
-            defaultQuestionUnit.setQuestionOptionList(defaultQuestionOptions);
-            defaultQuestionUnit.setRightAnswer(QuestionAnswerConvert.parseQuestionOptionAnswers(quesOptions));
-        } else {
-            defaultQuestionUnit.setRightAnswer(QuestionAnswerConvert.parseQuestionAnswers(question));
-        }
-
-        return defaultQuestionUnit;
-    }
-
-    /**
-     * html中添加音频标签
-     *
-     * @param defaultQuestion
-     * @param examId
-     * @param courseCode
-     * @param groupCode
-     * @param question
-     */
-    private void appendAudioFlag(DefaultQuestion defaultQuestion, String examId, String courseCode, String groupCode, Question question) {
-        if (question.getHasAudio() != null && question.getHasAudio() == true) {
-            //1.判断questionDto是否含有音频,如果有添加音频播放次数
-            AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo.findOne(Example.of(new AudioTimeConfig(examId, courseCode, groupCode, question.getId()))));
-            if (audioTimeConfig != null) {
-                //2.1 取到题干,给a标签添加url
-                String quesBody = null;
-                if (StringUtils.isNotBlank(defaultQuestion.getMasterVersion().getBody())) {
-                    //套题
-                    quesBody = defaultQuestion.getMasterVersion().getBody();
-                    defaultQuestion.getMasterVersion().setBody(buildBody(quesBody, audioTimeConfig.getPlayTime()));
-                } else {
-                    DefaultQuestionUnit defaultQuestionUnit = (DefaultQuestionUnit) defaultQuestion.getMasterVersion().getQuestionUnitList().get(0);
-                    quesBody = defaultQuestionUnit.getBody();
-                    defaultQuestionUnit.setBody(buildBody(quesBody, audioTimeConfig.getPlayTime()));
-                    if (defaultQuestionUnit.getQuestionType() == QuestionType.SINGLE_CHOICE || defaultQuestionUnit.getQuestionType() == QuestionType.MULTIPLE_CHOICE) {
-                        List<DefaultQuestionOption> questionOptionList = defaultQuestionUnit.getQuestionOptionList();
-                        if (questionOptionList != null && questionOptionList.size() > 0) {
-                            for (int i = 0; i < questionOptionList.size(); i++) {
-                                DefaultQuestionOption defaultQuestionOption = questionOptionList.get(i);
-                                defaultQuestionOption.setBody(buildBody(defaultQuestionOption.getBody(), audioTimeConfig.getPlayTime()));
-                            }
-                        }
-                    }
-                }
-            } else {
-                String quesBody = null;
-                if (StringUtils.isNotBlank(defaultQuestion.getMasterVersion().getBody())) {
-                    //套题
-                    quesBody = defaultQuestion.getMasterVersion().getBody();
-                    defaultQuestion.getMasterVersion().setBody(buildBody(quesBody, null));
-                } else {
-                    DefaultQuestionUnit defaultQuestionUnit = (DefaultQuestionUnit) defaultQuestion.getMasterVersion().getQuestionUnitList().get(0);
-                    quesBody = defaultQuestionUnit.getBody();
-                    defaultQuestionUnit.setBody(buildBody(quesBody, null));
-                    if (defaultQuestionUnit.getQuestionType() == QuestionType.SINGLE_CHOICE || defaultQuestionUnit.getQuestionType() == QuestionType.MULTIPLE_CHOICE) {
-                        List<DefaultQuestionOption> questionOptionList = defaultQuestionUnit.getQuestionOptionList();
-                        if (questionOptionList != null && questionOptionList.size() > 0) {
-                            for (int i = 0; i < questionOptionList.size(); i++) {
-                                DefaultQuestionOption defaultQuestionOption = questionOptionList.get(i);
-                                defaultQuestionOption.setBody(buildBody(defaultQuestionOption.getBody(), null));
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * 给题目和选项添加url
-     *
-     * @param body
-     * @param playTime
-     * @return
-     */
-    public String buildBody(String body, Integer playTime) {
-        String[] bodyStrings = body.split("></a>");
-        if (bodyStrings.length > 1) {
-            String resultBody = "";
-            for (int i = 0; i < bodyStrings.length; i++) {
-                String containAStr = bodyStrings[i];
-                if (containAStr.indexOf("<a") > -1) {
-                    String questionAudioId = matchAudioName(containAStr, "a", "id");
-                    QuestionAudio questionAudio = questionAudioService.findAudioById(questionAudioId);
-                    if (questionAudio != null) {
-//                        String url = sysProperty.getDomain() + questionAudio.getFileUrl();
-                    	//通用存储
-                        String url = FileStorageUtil.realPath(questionAudio.getFileUrl());
-                        if (playTime != null) {
-                            containAStr += " question-audio url=\"" + url + "\" playTime=\"" + playTime + "\"" + "></a>";
-                        } else {
-                            containAStr += " question-audio url=\"" + url + "\"" + "></a>";
-                        }
-                    }
-                }
-                resultBody += containAStr;
-            }
-            return resultBody;
-        } else {
-            return body;
-        }
-    }
-
-    /**
-     * 获取一段html中某个标签的值
-     *
-     * @param source
-     * @param element
-     * @param attr
-     * @return
-     */
-    private String matchAudioName(String source, String element, String attr) {
-        String reg = "<" + element + "[^<>]*?\\s" + attr + "=['\"]?(.*?)['\"]?(\\s.*?)";
-        Matcher m = Pattern.compile(reg).matcher(source);
-        if (m.find()) {
-            return m.group(1);
-        }
-        return "";
-    }
-
-    /**
-     * 检查所有题目是否为客观题
-     *
-     * @param paperDetailUnits
-     * @return
-     */
-    private boolean checkIsAllQbjectiveQuestion(List<PaperDetailUnit> paperDetailUnits) {
-        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-            Question question = paperDetailUnit.getQuestion();
-            //填空或问答
-            if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
-                    || question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
-                return false;
-            }
-            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                List<Question> subQuestions = question.getSubQuestions();
-                for (Question subQuestion : subQuestions) {
-                    if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
-                            || subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
-                        return false;
-                    }
-                }
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public DefaultPaper getBaseDefaultPaper(String paperId) {
-        Paper basePaper = Model.of(paperRepo.findById(paperId));
-        if (basePaper == null) {
-            LOG.error("该考试和课程下调卷规则中该类型试卷不存在,请检查调卷规则,调卷程序退出");
-            throw new StatusException("Q-020560", "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则");
-        }
-
-        //构建试卷结构
-        DefaultPaper defaultPaper = this.buildDefaultByBasePaper(basePaper, null, null, null);
-        return defaultPaper;
-    }
-
-    @Override
-    public Map<String, DefaultQuestion> getDefaultQuestions(Set<String> questionIds) {
-        Map<String, DefaultQuestion> map = new HashMap<>();
-        List<Question> questions = quesRepo.findByIdIn(questionIds);
-        if (CollectionUtils.isEmpty(questions)) {
-            LOG.error("根据试题id的集合没有查询到试题结合");
-            throw new StatusException("Q-020572", "根据试题id的集合没有查询到试题结合");
-        }
-        for (Question question : questions) {
-            DefaultQuestion defaultQuestion = getDefaultQuestion(null, null, null, question.getId());
-            map.put(question.getId(), defaultQuestion);
-        }
-        return map;
-    }
-
-    @Override
-    public List<String> getAnswer(String questionId) {
-        List<String> list = new ArrayList<>();
-        Question question = Model.of(quesRepo.findById(questionId));
-
-        if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-            List<Question> subList = question.getSubQuestions();
-            if (subList != null && subList.size() > 0) {
-                String answer = "";
-                for (Question subQuestion : subList) {
-                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                        String[] answers = QuestionAnswerConvert.parseQuestionOptionAnswers(subQuestion.getQuesOptions());
-                        answer = StringUtils.join(answers, ",");
-                    } else if (subQuestion.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
-                        if (subQuestion.getQuesAnswer().endsWith("正确")) {
-                            answer = "true";
-                        } else {
-                            answer = "false";
-                        }
-                    } else {
-                        answer = subQuestion.getQuesAnswer();
-                    }
-                    list.add(answer);
-                }
-            }
-        } else {
-            String answer = "";
-            if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                String[] answers = QuestionAnswerConvert.parseQuestionOptionAnswers(question.getQuesOptions());
-                answer = StringUtils.join(answers, ",");
-            } else if (question.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
-                if (question.getQuesAnswer().endsWith("正确")) {
-                    answer = "true";
-                } else {
-                    answer = "false";
-                }
-            } else {
-                answer = question.getQuesAnswer();
-            }
-            list.add(answer);
-        }
-
-        return list;
-    }
-
-    @Override
-    public String getQuestionStructure(String paperId) throws Exception {
-        String json = paperService.findQuestionStructure(paperId);
-        return json;
-    }
+	private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigProviderServiceImpl.class);
+
+	@Autowired
+	private ExtractConfigService extractConfigService;
+
+	@Autowired
+	private PaperRepo paperRepo;
+
+	@Autowired
+	private PaperDetailRepo paperDetailRepo;
+
+	@Autowired
+	private PaperDetailUnitRepo paperDetailUnitRepo;
+
+	@Autowired
+	private QuesRepo quesRepo;
+
+	@Autowired
+	private AudioTimeConfigRepo audioTimeConfigRepo;
+
+	@Autowired
+	private QuestionAudioService questionAudioService;
+
+	@Autowired
+	private QuesService quesService;
+
+	@Autowired
+	private PaperService paperService;
+
+	// @Autowired
+	// private SysProperty sysProperty;
+
+	@Override
+	public ExtractConfigCacheBean getExtractConfig(Long examId, String courseCode) {
+		ExtractConfig extractConfig = extractConfigService.findConfig(new ExtractConfig(examId, courseCode));
+		if (extractConfig == null) {
+			throw new StatusException("500", "该课程调卷规则未制定,请联系学校!");
+		}
+		if (CallType.WHOLE_SET.equals(extractConfig.getCallType())) {
+			if (CollectionUtils.isEmpty(extractConfig.getExamPaperList())) {
+				throw new StatusException("500", "可供抽取的试卷集合为空!");
+			}
+		}
+
+		ExtractConfigCacheBean cacheBean = new ExtractConfigCacheBean();
+		cacheBean.setId(extractConfig.getId());
+		cacheBean.setExamId(examId);
+		cacheBean.setCallType(extractConfig.getCallType());
+		cacheBean.setCourseCode(courseCode);
+		cacheBean.setRandomPaperId(extractConfig.getRandomPaperId());
+		cacheBean.setPlayTime(extractConfig.getPlayTime());
+		cacheBean.setSortQuestionOrder(getByShort(extractConfig.getScrambling_the_question_order()));
+		cacheBean.setSortOptionOrder(getByShort(extractConfig.getScrambling_the_option_order()));
+
+		if (CallType.WHOLE_SET.equals(extractConfig.getCallType())) {
+			List<ExtractConfigDetailCacheBean> details = new ArrayList<>();
+			for (ExamPaper examPaper : extractConfig.getExamPaperList()) {
+				details.add(new ExtractConfigDetailCacheBean(examPaper.getGroupCode(), examPaper.getPaper().getId(),
+						examPaper.getWeight()));
+			}
+			cacheBean.setDetails(details);
+		}
+
+		return cacheBean;
+	}
+	
+	private boolean getByShort(Short s) {
+		if(s==null) {
+			return false;
+		}else {
+			return s!=0;
+		}
+	}
+
+	@Override
+	public ExtractConfigPaperCacheBean getExtractConfigPaper(Long examId, String courseCode, String groupCode,
+			String paperId) {
+		DefaultPaper defaultPaper = this.buildDefaultByBasePaper(paperId, examId, courseCode, groupCode);
+
+		ExtractConfigPaperCacheBean cacheBean = new ExtractConfigPaperCacheBean();
+		cacheBean.setDefaultPaper(defaultPaper);
+		return cacheBean;
+	}
+
+	@Override
+	public ExtractConfigPaper getDefaultPaper(Long examId, String courseCode, String groupCode) {
+		LOG.info("调卷开始...");
+
+		ExtractConfigCacheBean extractConfigCache = CacheHelper.getExtractConfig(examId, courseCode);
+		if (extractConfigCache == null) {
+			throw new StatusException("500", "该课程调卷规则未制定,请联系学校!");
+		}
+
+		final String msg = "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则!";
+		Map<String, String> paperTypeMaps = this.getExamPaperByProbability(extractConfigCache.getDetails());
+		if (paperTypeMaps.isEmpty()) {
+			throw new StatusException("500", msg);
+		}
+
+		String basePaperId = paperTypeMaps.get(groupCode);
+		if (StringUtils.isEmpty(basePaperId)) {
+			throw new StatusException("500", msg);
+		}
+
+		LOG.info("构建试卷结构..." + basePaperId);
+		ExtractConfigPaperCacheBean extractConfigPaperCache = CacheHelper.getExtractConfigPaper(examId, courseCode,
+				groupCode, basePaperId);
+		if (extractConfigPaperCache == null) {
+			throw new StatusException("500", msg);
+		}
+
+		ExtractConfigPaper result = new ExtractConfigPaper();
+		result.setDefaultPaper(extractConfigPaperCache.getDefaultPaper());
+		result.setPaperId(basePaperId);
+		result.setSortQuestionOrder(extractConfigCache.getSortQuestionOrder());
+		result.setSortOptionOrder(extractConfigCache.getSortOptionOrder());
+		return result;
+	}
+
+	/**
+	 * 每个试卷类型取出一套试卷 {A:paperId,B:paperId} A是试卷类型,paperId是A类型下选定的试卷ID
+	 */
+	private Map<String, String> getExamPaperByProbability(List<ExtractConfigDetailCacheBean> examPapers) {
+		if (CollectionUtils.isEmpty(examPapers)) {
+			throw new StatusException("500", "可供抽取的试卷集合为空!");
+		}
+
+		Map<String, List<ExtractConfigDetailCacheBean>> examPaperMaps = new HashMap<>();
+		for (ExtractConfigDetailCacheBean examPaper : examPapers) {
+			if (examPaperMaps.containsKey(examPaper.getGroupCode())) {
+				examPaperMaps.get(examPaper.getGroupCode()).add(examPaper);
+			} else {
+				List<ExtractConfigDetailCacheBean> list = new ArrayList<>();
+				list.add(examPaper);
+				examPaperMaps.put(examPaper.getGroupCode(), list);
+			}
+		}
+
+		Map<String, String> paperTypeMaps = new HashMap<>();
+		for (Map.Entry<String, List<ExtractConfigDetailCacheBean>> entry : examPaperMaps.entrySet()) {
+			List<ExtractConfigDetailCacheBean> list = examPaperMaps.get(entry.getKey());
+
+			String paperId = this.getPaperByProbability(list);
+			if (StringUtils.isEmpty(paperId)) {
+				continue;
+			}
+
+			paperTypeMaps.put(entry.getKey(), paperId);
+		}
+
+		return paperTypeMaps;
+	}
+
+	/**
+	 * 根据设定几率取出一套试卷
+	 */
+	private String getPaperByProbability(List<ExtractConfigDetailCacheBean> examPapers) {
+		int sum = 0;
+		for (ExtractConfigDetailCacheBean examPaper : examPapers) {
+			sum += examPaper.getWeight();
+		}
+
+		// 从1开始
+		int r = new Random().nextInt(sum) + 1;
+		for (ExtractConfigDetailCacheBean examPaper : examPapers) {
+			r -= examPaper.getWeight();
+			if (r <= 0) {
+				return examPaper.getPaperId();// 选中
+			}
+		}
+
+		return null;
+	}
+
+	/**
+	 * 根据paper对象构建DefaultPaper对象
+	 */
+	public DefaultPaper buildDefaultByBasePaper(String basePaperId, Long examId, String courseCode, String groupCode) {
+		Paper basePaper = Model.of(paperRepo.findById(basePaperId));
+		return this.buildDefaultByBasePaper(basePaper, examId, courseCode, groupCode);
+	}
+
+	/**
+	 * 根据paper对象构建DefaultPaper对象
+	 */
+	public DefaultPaper buildDefaultByBasePaper(Paper basePaper, Long examId, String courseCode, String groupCode) {
+		LOG.debug("开始包装网考需要的试卷结构...");
+
+		// 获取大题
+		List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(basePaper);
+
+		// 排序大题
+		Collections.sort(paperDetails);
+
+		// 将小题全部取出来,只取一次,减少对数据库的查询
+		List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaper(basePaper);
+		boolean fullyObjective = checkIsAllQbjectiveQuestion(allPaperDetailUnits);
+
+		// 根据大题id将小题归类
+		Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
+				.collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
+
+		LOG.debug("循环大题,开始组装对象...");
+
+		// 生成新的分组集合
+		List<DefaultQuestionGroup> questionGroupList = new ArrayList<>();
+		for (int i = 0; i < paperDetails.size(); i++) {
+			PaperDetail paperDetail = paperDetails.get(i);
+			DefaultQuestionGroup defaultQuestionGroup = new DefaultQuestionGroup();
+			defaultQuestionGroup.setGroupName(paperDetail.getName());
+			defaultQuestionGroup.setGroupScore(paperDetail.getScore());
+
+			// 获取原小题的集合
+			List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
+			if (CollectionUtils.isEmpty(paperDetailUnits)) {
+				LOG.warn("试卷大题下面没有小题!");
+				throw new StatusException("500", "考试试卷有误,请联系老师!");
+			}
+
+			// 按题号顺序排序
+			Collections.sort(paperDetailUnits);
+
+			// 生成新的题包装器集合
+			List<DefaultQuestionStructureWrapper> questionWrapperList = new ArrayList<>();
+			for (int j = 0; j < paperDetailUnits.size(); j++) {
+				PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
+				DefaultQuestionStructureWrapper defaultQuestionStructureWrapper = new DefaultQuestionStructureWrapper();
+				defaultQuestionStructureWrapper.setQuestionId(paperDetailUnit.getQuestion().getId());
+				defaultQuestionStructureWrapper.setVersion(CommonUtils.QUESTION_VERSION);
+				defaultQuestionStructureWrapper.setQuestionScore(paperDetailUnit.getScore());
+				defaultQuestionStructureWrapper.setTimeLimit(paperDetailUnit.getTimeLimit());
+
+				if (examId != null) {
+					// 设置音频播放次数
+					if (paperDetailUnit.getQuestion().getHasAudio() != null
+							&& paperDetailUnit.getQuestion().getHasAudio()) {
+						AudioTimeConfig find = new AudioTimeConfig(examId.toString(), courseCode, groupCode,
+								paperDetailUnit.getQuestion().getId());
+						AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo.findOne(Example.of(find)));
+						if (audioTimeConfig != null) {
+							defaultQuestionStructureWrapper.setLimitedPlayTimes(audioTimeConfig.getPlayTime());
+						}
+					}
+				}
+
+				// 生成新的题单元包装器
+				List<DefaultQuestionUnitWrapper> defaultQuestionUnitWrappers = new ArrayList<>();
+				if (paperDetailUnit.getQuestion().getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+					List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
+					List<Double> scoreList = paperDetailUnit.getSubScoreList();
+					if (subQuesList != null && subQuesList.size() > 0) {
+						for (int k = 0; k < subQuesList.size(); k++) {
+							DefaultQuestionUnitWrapper defaultQuestionUnitWrapper = buildQuesUnitWrapper(
+									subQuesList.get(k), scoreList.get(k));
+							defaultQuestionUnitWrappers.add(defaultQuestionUnitWrapper);
+						}
+					}
+				} else {
+					DefaultQuestionUnitWrapper defaultQuestionUnitWrapper = buildQuesUnitWrapper(
+							paperDetailUnit.getQuestion(), paperDetailUnit.getScore());
+					defaultQuestionUnitWrappers.add(defaultQuestionUnitWrapper);
+				}
+
+				defaultQuestionStructureWrapper.setQuestionUnitWrapperList(defaultQuestionUnitWrappers);
+				questionWrapperList.add(defaultQuestionStructureWrapper);
+			}
+
+			defaultQuestionGroup.setQuestionWrapperList(questionWrapperList);
+			questionGroupList.add(defaultQuestionGroup);
+		}
+
+		DefaultPaper defaultPaper = new DefaultPaper();
+		defaultPaper.setName(basePaper.getName());
+		defaultPaper.setQuestionGroupList(questionGroupList);
+		defaultPaper.setFullyObjective(fullyObjective);
+
+		return defaultPaper;
+	}
+
+	/**
+	 * 根据question生成题单元包装器
+	 *
+	 * @param question
+	 * @param score
+	 * @return
+	 */
+	private DefaultQuestionUnitWrapper buildQuesUnitWrapper(Question question, Double score) {
+		Integer[] optionPermutation = null;
+		if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+				|| question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+			int length = question.getQuesOptions().size();
+			optionPermutation = new Integer[length];
+
+			for (int i = 0; i < length; i++) {
+				optionPermutation[i] = i;
+			}
+		}
+
+		DefaultQuestionUnitWrapper defaultQuestionUnitWrapper = new DefaultQuestionUnitWrapper();
+		defaultQuestionUnitWrapper.setOptionPermutation(optionPermutation);
+		defaultQuestionUnitWrapper.setQuestionScore(score);
+		defaultQuestionUnitWrapper.setQuestionType(getByOldType(question.getQuestionType()));
+		defaultQuestionUnitWrapper.setAnswerType(question.getAnswerType());// 作答类型
+		return defaultQuestionUnitWrapper;
+	}
+
+	/**
+	 * 题型转换方法
+	 *
+	 * @param quesStructType
+	 * @return
+	 */
+	private QuestionType getByOldType(QuesStructType quesStructType) {
+		if (quesStructType == QuesStructType.BOOL_ANSWER_QUESTION) {
+			return QuestionType.TRUE_OR_FALSE;
+		}
+		if (quesStructType == QuesStructType.FILL_BLANK_QUESTION) {
+			return QuestionType.FILL_UP;
+		}
+		if (quesStructType == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+			return QuestionType.MULTIPLE_CHOICE;
+		}
+		if (quesStructType == QuesStructType.SINGLE_ANSWER_QUESTION) {
+			return QuestionType.SINGLE_CHOICE;
+		}
+		if (quesStructType == QuesStructType.TEXT_ANSWER_QUESTION) {
+			return QuestionType.ESSAY;
+		}
+		return null;
+	}
+
+	@Override
+	public DefaultQuestion getDefaultQuestion(Long examId, String courseCode, String groupCode, String questionId) {
+		Question question = Model.of(quesRepo.findById(questionId));
+		if (question == null) {
+			throw new StatusException("500", "试题不存在!");
+		}
+		quesService.formatQues(question);
+
+		// 封装成新的题单元集合
+		DefaultQuestionStructure defaultQuestionStructure = new DefaultQuestionStructure();
+		if (question.getHasAudio() == null || false == question.getHasAudio()) {
+			defaultQuestionStructure.setHasAudios(false);
+		} else {
+			defaultQuestionStructure.setHasAudios(true);
+		}
+		defaultQuestionStructure.setVersion(CommonUtils.QUESTION_VERSION);
+
+		List<DefaultQuestionUnit> questionUnitList = new ArrayList<>();
+		if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+			defaultQuestionStructure.setBody(question.getQuesBody());
+
+			// 获取套题下面所有子题
+			List<Question> subQuesList = question.getSubQuestions();
+			if (CollectionUtils.isNotEmpty(subQuesList)) {
+				for (int i = 0; i < subQuesList.size(); i++) {
+					Question subQuestion = subQuesList.get(i);
+					DefaultQuestionUnit defaultQuestionUnit = buildQuestionUnit(subQuestion);
+					questionUnitList.add(defaultQuestionUnit);
+				}
+			}
+		} else {
+			DefaultQuestionUnit defaultQuestionUnit = buildQuestionUnit(question);
+			questionUnitList.add(defaultQuestionUnit);
+		}
+
+		defaultQuestionStructure.setQuestionUnitList(questionUnitList);
+		DefaultQuestion defaultQuestion = new DefaultQuestion();
+		defaultQuestion.setId(questionId);
+		defaultQuestion.setMasterVersion(defaultQuestionStructure);
+
+		if (examId != null) {
+			appendAudioFlag(defaultQuestion, String.valueOf(examId), courseCode, groupCode, question);
+		}
+
+		// 获取试题信息带答案(敏感信息日志,用于排查调用者)
+		LOG.warn("$$$getQuestionWithAnswer questionId:{} examId:{} courseCode:{} groupCode:{}", questionId, examId,
+				courseCode, groupCode);
+		return defaultQuestion;
+	}
+
+	/**
+	 * 构建试题单元
+	 *
+	 * @param question
+	 * @return
+	 */
+	private DefaultQuestionUnit buildQuestionUnit(Question question) {
+		DefaultQuestionUnit defaultQuestionUnit = new DefaultQuestionUnit();
+		defaultQuestionUnit.setAnswerType(question.getAnswerType());// 作答类型
+		defaultQuestionUnit.setBody(question.getQuesBody());
+		defaultQuestionUnit.setQuestionType(getByOldType(question.getQuestionType()));
+
+		// 如果是单选或者多选,添加选项和答案转换
+		if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+				|| question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+			List<DefaultQuestionOption> defaultQuestionOptions = new ArrayList<>();
+			List<QuesOption> quesOptions = question.getQuesOptions();
+
+			if (quesOptions != null && quesOptions.size() > 0) {
+				for (int i = 0; i < quesOptions.size(); i++) {
+					QuesOption quesOption = quesOptions.get(i);
+					DefaultQuestionOption defaultQuestionOption = new DefaultQuestionOption();
+					defaultQuestionOption.setBody(quesOption.getOptionBody());
+					defaultQuestionOptions.add(defaultQuestionOption);
+				}
+			}
+			defaultQuestionUnit.setQuestionOptionList(defaultQuestionOptions);
+			defaultQuestionUnit.setRightAnswer(QuestionAnswerConvert.parseQuestionOptionAnswers(quesOptions));
+		} else {
+			defaultQuestionUnit.setRightAnswer(QuestionAnswerConvert.parseQuestionAnswers(question));
+		}
+
+		return defaultQuestionUnit;
+	}
+
+	/**
+	 * html中添加音频标签
+	 *
+	 * @param defaultQuestion
+	 * @param examId
+	 * @param courseCode
+	 * @param groupCode
+	 * @param question
+	 */
+	private void appendAudioFlag(DefaultQuestion defaultQuestion, String examId, String courseCode, String groupCode,
+			Question question) {
+		if (question.getHasAudio() != null && question.getHasAudio() == true) {
+			// 1.判断questionDto是否含有音频,如果有添加音频播放次数
+			AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo
+					.findOne(Example.of(new AudioTimeConfig(examId, courseCode, groupCode, question.getId()))));
+			if (audioTimeConfig != null) {
+				// 2.1 取到题干,给a标签添加url
+				String quesBody = null;
+				if (StringUtils.isNotBlank(defaultQuestion.getMasterVersion().getBody())) {
+					// 套题
+					quesBody = defaultQuestion.getMasterVersion().getBody();
+					defaultQuestion.getMasterVersion().setBody(buildBody(quesBody, audioTimeConfig.getPlayTime()));
+				} else {
+					DefaultQuestionUnit defaultQuestionUnit = (DefaultQuestionUnit) defaultQuestion.getMasterVersion()
+							.getQuestionUnitList().get(0);
+					quesBody = defaultQuestionUnit.getBody();
+					defaultQuestionUnit.setBody(buildBody(quesBody, audioTimeConfig.getPlayTime()));
+					if (defaultQuestionUnit.getQuestionType() == QuestionType.SINGLE_CHOICE
+							|| defaultQuestionUnit.getQuestionType() == QuestionType.MULTIPLE_CHOICE) {
+						List<DefaultQuestionOption> questionOptionList = defaultQuestionUnit.getQuestionOptionList();
+						if (questionOptionList != null && questionOptionList.size() > 0) {
+							for (int i = 0; i < questionOptionList.size(); i++) {
+								DefaultQuestionOption defaultQuestionOption = questionOptionList.get(i);
+								defaultQuestionOption.setBody(
+										buildBody(defaultQuestionOption.getBody(), audioTimeConfig.getPlayTime()));
+							}
+						}
+					}
+				}
+			} else {
+				String quesBody = null;
+				if (StringUtils.isNotBlank(defaultQuestion.getMasterVersion().getBody())) {
+					// 套题
+					quesBody = defaultQuestion.getMasterVersion().getBody();
+					defaultQuestion.getMasterVersion().setBody(buildBody(quesBody, null));
+				} else {
+					DefaultQuestionUnit defaultQuestionUnit = (DefaultQuestionUnit) defaultQuestion.getMasterVersion()
+							.getQuestionUnitList().get(0);
+					quesBody = defaultQuestionUnit.getBody();
+					defaultQuestionUnit.setBody(buildBody(quesBody, null));
+					if (defaultQuestionUnit.getQuestionType() == QuestionType.SINGLE_CHOICE
+							|| defaultQuestionUnit.getQuestionType() == QuestionType.MULTIPLE_CHOICE) {
+						List<DefaultQuestionOption> questionOptionList = defaultQuestionUnit.getQuestionOptionList();
+						if (questionOptionList != null && questionOptionList.size() > 0) {
+							for (int i = 0; i < questionOptionList.size(); i++) {
+								DefaultQuestionOption defaultQuestionOption = questionOptionList.get(i);
+								defaultQuestionOption.setBody(buildBody(defaultQuestionOption.getBody(), null));
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * 给题目和选项添加url
+	 *
+	 * @param body
+	 * @param playTime
+	 * @return
+	 */
+	public String buildBody(String body, Integer playTime) {
+		String[] bodyStrings = body.split("></a>");
+		if (bodyStrings.length > 1) {
+			String resultBody = "";
+			for (int i = 0; i < bodyStrings.length; i++) {
+				String containAStr = bodyStrings[i];
+				if (containAStr.indexOf("<a") > -1) {
+					String questionAudioId = matchAudioName(containAStr, "a", "id");
+					QuestionAudio questionAudio = questionAudioService.findAudioById(questionAudioId);
+					if (questionAudio != null) {
+						// String url = sysProperty.getDomain() + questionAudio.getFileUrl();
+						// 通用存储
+						String url = FileStorageUtil.realPath(questionAudio.getFileUrl());
+						if (playTime != null) {
+							containAStr += " question-audio url=\"" + url + "\" playTime=\"" + playTime + "\""
+									+ "></a>";
+						} else {
+							containAStr += " question-audio url=\"" + url + "\"" + "></a>";
+						}
+					}
+				}
+				resultBody += containAStr;
+			}
+			return resultBody;
+		} else {
+			return body;
+		}
+	}
+
+	/**
+	 * 获取一段html中某个标签的值
+	 *
+	 * @param source
+	 * @param element
+	 * @param attr
+	 * @return
+	 */
+	private String matchAudioName(String source, String element, String attr) {
+		String reg = "<" + element + "[^<>]*?\\s" + attr + "=['\"]?(.*?)['\"]?(\\s.*?)";
+		Matcher m = Pattern.compile(reg).matcher(source);
+		if (m.find()) {
+			return m.group(1);
+		}
+		return "";
+	}
+
+	/**
+	 * 检查所有题目是否为客观题
+	 *
+	 * @param paperDetailUnits
+	 * @return
+	 */
+	private boolean checkIsAllQbjectiveQuestion(List<PaperDetailUnit> paperDetailUnits) {
+		for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+			Question question = paperDetailUnit.getQuestion();
+			// 填空或问答
+			if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
+					|| question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
+				return false;
+			}
+			if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+				List<Question> subQuestions = question.getSubQuestions();
+				for (Question subQuestion : subQuestions) {
+					if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
+							|| subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
+						return false;
+					}
+				}
+			}
+		}
+		return true;
+	}
+
+	@Override
+	public DefaultPaper getBaseDefaultPaper(String paperId) {
+		Paper basePaper = Model.of(paperRepo.findById(paperId));
+		if (basePaper == null) {
+			LOG.error("该考试和课程下调卷规则中该类型试卷不存在,请检查调卷规则,调卷程序退出");
+			throw new StatusException("Q-020560", "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则");
+		}
+
+		// 构建试卷结构
+		DefaultPaper defaultPaper = this.buildDefaultByBasePaper(basePaper, null, null, null);
+		return defaultPaper;
+	}
+
+	@Override
+	public Map<String, DefaultQuestion> getDefaultQuestions(Set<String> questionIds) {
+		Map<String, DefaultQuestion> map = new HashMap<>();
+		List<Question> questions = quesRepo.findByIdIn(questionIds);
+		if (CollectionUtils.isEmpty(questions)) {
+			LOG.error("根据试题id的集合没有查询到试题结合");
+			throw new StatusException("Q-020572", "根据试题id的集合没有查询到试题结合");
+		}
+		for (Question question : questions) {
+			DefaultQuestion defaultQuestion = getDefaultQuestion(null, null, null, question.getId());
+			map.put(question.getId(), defaultQuestion);
+		}
+		return map;
+	}
+
+	@Override
+	public List<String> getAnswer(String questionId) {
+		List<String> list = new ArrayList<>();
+		Question question = Model.of(quesRepo.findById(questionId));
+
+		if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+			List<Question> subList = question.getSubQuestions();
+			if (subList != null && subList.size() > 0) {
+				String answer = "";
+				for (Question subQuestion : subList) {
+					if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+							|| subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+						String[] answers = QuestionAnswerConvert
+								.parseQuestionOptionAnswers(subQuestion.getQuesOptions());
+						answer = StringUtils.join(answers, ",");
+					} else if (subQuestion.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+						if (subQuestion.getQuesAnswer().endsWith("正确")) {
+							answer = "true";
+						} else {
+							answer = "false";
+						}
+					} else {
+						answer = subQuestion.getQuesAnswer();
+					}
+					list.add(answer);
+				}
+			}
+		} else {
+			String answer = "";
+			if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+					|| question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+				String[] answers = QuestionAnswerConvert.parseQuestionOptionAnswers(question.getQuesOptions());
+				answer = StringUtils.join(answers, ",");
+			} else if (question.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+				if (question.getQuesAnswer().endsWith("正确")) {
+					answer = "true";
+				} else {
+					answer = "false";
+				}
+			} else {
+				answer = question.getQuesAnswer();
+			}
+			list.add(answer);
+		}
+
+		return list;
+	}
+
+	@Override
+	public String getQuestionStructure(String paperId) throws Exception {
+		String json = paperService.findQuestionStructure(paperId);
+		return json;
+	}
 
 }

+ 1313 - 1293
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/ExtractConfigServiceImpl.java

@@ -1,39 +1,5 @@
 package cn.com.qmth.examcloud.core.questions.service.impl;
 
-import static cn.com.qmth.examcloud.core.questions.service.cache.Constants.CACHE_KEY_PAPER_FOR_DTO;
-import static cn.com.qmth.examcloud.core.questions.service.cache.Constants.DEFAULT_TIME_OUT;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-
-import org.apache.commons.lang3.StringUtils;
-import org.bson.types.ObjectId;
-import org.nlpcn.commons.lang.util.StringUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.BeanUtils;
-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.data.domain.PageRequest;
-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.data.mongodb.core.query.Update;
-import org.springframework.stereotype.Service;
-import org.springframework.util.Assert;
-import org.springframework.util.CollectionUtils;
-
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
@@ -46,36 +12,14 @@ import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
 import cn.com.qmth.examcloud.core.questions.base.Model;
 import cn.com.qmth.examcloud.core.questions.base.em.enums.ExamType;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
-import cn.com.qmth.examcloud.core.questions.base.question.PaperDetailDto;
-import cn.com.qmth.examcloud.core.questions.base.question.PaperDetailUnitDto;
-import cn.com.qmth.examcloud.core.questions.base.question.PaperDto;
-import cn.com.qmth.examcloud.core.questions.base.question.QuesOptionDto;
-import cn.com.qmth.examcloud.core.questions.base.question.QuestionDto;
-import cn.com.qmth.examcloud.core.questions.base.question.SubQuestionDto;
+import cn.com.qmth.examcloud.core.questions.base.question.*;
 import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
-import cn.com.qmth.examcloud.core.questions.dao.AudioTimeConfigRepo;
-import cn.com.qmth.examcloud.core.questions.dao.ExtractConfigRepo;
-import cn.com.qmth.examcloud.core.questions.dao.PaperDetailRepo;
-import cn.com.qmth.examcloud.core.questions.dao.PaperDetailUnitRepo;
-import cn.com.qmth.examcloud.core.questions.dao.PaperRepo;
-import cn.com.qmth.examcloud.core.questions.dao.entity.AudioTimeConfig;
-import cn.com.qmth.examcloud.core.questions.dao.entity.Course;
-import cn.com.qmth.examcloud.core.questions.dao.entity.ExamPaper;
-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.PaperDetail;
-import cn.com.qmth.examcloud.core.questions.dao.entity.PaperDetailUnit;
-import cn.com.qmth.examcloud.core.questions.dao.entity.QuesOption;
-import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
-import cn.com.qmth.examcloud.core.questions.dao.entity.QuestionAudio;
+import cn.com.qmth.examcloud.core.questions.dao.*;
+import cn.com.qmth.examcloud.core.questions.dao.entity.*;
 import cn.com.qmth.examcloud.core.questions.service.ExtractConfigService;
 import cn.com.qmth.examcloud.core.questions.service.QuesService;
 import cn.com.qmth.examcloud.core.questions.service.QuestionAudioService;
-import cn.com.qmth.examcloud.core.questions.service.bean.CouresInfo;
-import cn.com.qmth.examcloud.core.questions.service.bean.PaperDetailDtoAssembler;
-import cn.com.qmth.examcloud.core.questions.service.bean.PaperDetailUnitDtoAssembler;
-import cn.com.qmth.examcloud.core.questions.service.bean.PaperDtoAssembler;
-import cn.com.qmth.examcloud.core.questions.service.bean.SubQuestionDtoAssembler;
+import cn.com.qmth.examcloud.core.questions.service.bean.*;
 import cn.com.qmth.examcloud.examwork.api.ExamCloudService;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamBean;
 import cn.com.qmth.examcloud.examwork.api.bean.ExamCourseRelationBean;
@@ -83,8 +27,35 @@ import cn.com.qmth.examcloud.examwork.api.request.GetExamCourseListReq;
 import cn.com.qmth.examcloud.examwork.api.request.GetExamReq;
 import cn.com.qmth.examcloud.examwork.api.response.GetExamCourseListResp;
 import cn.com.qmth.examcloud.examwork.api.response.GetExamResp;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
+import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
+import org.nlpcn.commons.lang.util.StringUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+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.data.domain.PageRequest;
+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.data.mongodb.core.query.Update;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static cn.com.qmth.examcloud.core.questions.service.cache.Constants.DEFAULT_TIME_OUT;
 
 /**
  * @author chenken
@@ -94,1241 +65,1290 @@ import cn.com.qmth.examcloud.web.redis.RedisClient;
  */
 @Service("extractConfigService")
 public class ExtractConfigServiceImpl implements ExtractConfigService {
-	private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigServiceImpl.class);
-
-	@Autowired
-	private ExtractConfigRepo extractConfigRepo;
-
-	@Autowired
-	private PaperDetailRepo paperDetailRepo;
-
-	@Autowired
-	private PaperDetailUnitRepo paperDetailUnitRepo;
-
-	@Autowired
-	private AudioTimeConfigRepo audioTimeConfigRepo;
-
-	@Autowired
-	private PaperRepo paperRepo;
-
-	@Autowired
-	private QuesService quesService;
-
-	@Autowired
-	private QuestionAudioService questionAudioService;
-
-	@Autowired
-	private MongoTemplate mongoTemplate;
-
-	@Autowired
-	private CourseService courseService;
-//
-//    @Autowired
-//    private SysProperty sysProperty;
-
-	@Autowired
-	private PaperDtoAssembler paperDtoAssembler;
-
-	@Autowired
-	private PaperDetailDtoAssembler paperDetailDtoAssembler;
 
-	@Autowired
-	private PaperDetailUnitDtoAssembler paperDetailUnitDtoAssembler;
-
-	@Autowired
-	private SubQuestionDtoAssembler subQuestionDtoAssembler;
-
-	@Autowired
-	private RedisClient redisClient;
-
-	/*
-	 * @Autowired private PaperDetailUnitNativeRepo detailUnitNativeRepo;
-	 */
-
-	@Autowired
-	private ExamCloudService examCloudService;
-	@Autowired
-	private ExamRecordCloudService adminExamRecordCloudService;
-	@Autowired
-	private ExamRecordDataCloudService studentExamRecordCloudService;
-
-	@Override
-	public ExtractConfig findConfig(ExtractConfig condition) {
-		if (condition.getExamId() == null) {
-			return null;
-		}
-		if (StringUtils.isBlank(condition.getCourseCode())) {
-			return null;
-		}
-		Query query = new Query();
-		if (!StringUtils.isBlank(condition.getOrgId())) {
-			query.addCriteria(Criteria.where("orgId").is(condition.getOrgId()));
-		}
-		query.addCriteria(Criteria.where("examId").is(condition.getExamId()));
-		query.addCriteria(Criteria.where("courseCode").is(condition.getCourseCode()));
-		ExtractConfig tempConfig = this.mongoTemplate.findOne(query, ExtractConfig.class);
-		return tempConfig;
-	}
-
-	@Override
-	public Map<String, String> saveExtractConfig2(ExtractConfig extractConfig, User user) throws Exception {
-		List<ExamPaper> examPapers = extractConfig.getExamPaperList();
-		for (int i = 0; i < examPapers.size(); i++) {
-			ExamPaper examPaper = examPapers.get(i);
-			Paper paper = examPaper.getPaper();
-			paper = Model.of(paperRepo.findById(paper.getId()));
-			examPaper.setPaper(paper);
-		}
-		Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
-		extractConfig.setCourse(course);
-		extractConfig.setCourseName(course.getName());
-		Map<String, String> newFinishedPaperIdMap = makePaperByConfig(extractConfig);
-		extractConfig.setFinishedPaperIdMap(newFinishedPaperIdMap);
-		extractConfig.setIfFinish((short) 1);
-		extractConfigRepo.save(extractConfig);
-		return newFinishedPaperIdMap;
-	}
-
-	@Override
-	public List<ExamPaper> saveExtractConfig(ExtractConfig extractConfig, User user) {
-		List<ExamPaper> examPapers = extractConfig.getExamPaperList();
-		for (int i = 0; i < examPapers.size(); i++) {
-			ExamPaper examPaper = examPapers.get(i);
-			Paper paper = examPaper.getPaper();
-			paper = Model.of(paperRepo.findById(paper.getId()));
-			examPaper.setPaper(paper);
-			if (ExamType.OFFLINE.name().equals(extractConfig.getExamType())) {
-				checkOfflinePaper(paper);
-			}
-		}
-		if(StringUtils.isNotBlank(extractConfig.getId())) {
-			disposeOldPaper(extractConfig.getId());
-		}
-		Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
-		extractConfig.setCourse(course);
-		extractConfig.setCourseName(course.getName());
-		extractConfig.setIfFinish((short) 1);
-		extractConfigRepo.save(extractConfig);
-		disposeNowPaper(extractConfig.getId());
-		return examPapers;
-	}
-
-	/**先处理原来绑定的试卷
-	 * @param extractConfigId
-	 */
-	private void disposeOldPaper(String extractConfigId) {
-		ExtractConfig ec = Model.of(extractConfigRepo.findById(extractConfigId));
-		for (ExamPaper paper : ec.getExamPaperList()) {
-			if(inExam(paper.getPaper().getId())) {//如果有考试记录
-				updatePaperInUse(paper.getPaper().getId(), 1);
-			}else {
-				if(!inOtherExtractConfig(extractConfigId, paper.getPaper().getId())) {//没有考试记录且不被其他调卷规则引用
-					updatePaperInUse(paper.getPaper().getId(), 0);
-				}
+    private static final Logger LOG = LoggerFactory.getLogger(ExtractConfigServiceImpl.class);
+
+    @Autowired
+    private ExtractConfigRepo extractConfigRepo;
+
+    @Autowired
+    private PaperDetailRepo paperDetailRepo;
+
+    @Autowired
+    private PaperDetailUnitRepo paperDetailUnitRepo;
+
+    @Autowired
+    private AudioTimeConfigRepo audioTimeConfigRepo;
+
+    @Autowired
+    private PaperRepo paperRepo;
+
+    @Autowired
+    private QuesService quesService;
+
+    @Autowired
+    private QuestionAudioService questionAudioService;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    @Autowired
+    private CourseService courseService;
+
+    @Autowired
+    private RandomPaperRepo randomPaperRepo;
+
+    @Autowired
+    private PaperDtoAssembler paperDtoAssembler;
+
+    @Autowired
+    private PaperDetailDtoAssembler paperDetailDtoAssembler;
+
+    @Autowired
+    private PaperDetailUnitDtoAssembler paperDetailUnitDtoAssembler;
+
+    @Autowired
+    private SubQuestionDtoAssembler subQuestionDtoAssembler;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    /*
+     * @Autowired private PaperDetailUnitNativeRepo detailUnitNativeRepo;
+     */
+
+    @Autowired
+    private ExamCloudService examCloudService;
+
+    @Autowired
+    private ExamRecordCloudService adminExamRecordCloudService;
+
+    @Autowired
+    private ExamRecordDataCloudService studentExamRecordCloudService;
+
+    @Override
+    public ExtractConfig findConfig(ExtractConfig condition) {
+        if (condition.getExamId() == null) {
+            return null;
+        }
+        if (StringUtils.isBlank(condition.getCourseCode())) {
+            return null;
+        }
+        Query query = new Query();
+        if (!StringUtils.isBlank(condition.getOrgId())) {
+            query.addCriteria(Criteria.where("orgId").is(condition.getOrgId()));
+        }
+        query.addCriteria(Criteria.where("examId").is(condition.getExamId()));
+        query.addCriteria(Criteria.where("courseCode").is(condition.getCourseCode()));
+        ExtractConfig tempConfig = this.mongoTemplate.findOne(query, ExtractConfig.class);
+        return tempConfig;
+    }
+
+    @Override
+    public Map<String, String> saveExtractConfig2(ExtractConfig extractConfig, User user) throws Exception {
+        List<ExamPaper> examPapers = extractConfig.getExamPaperList();
+        for (int i = 0; i < examPapers.size(); i++) {
+            ExamPaper examPaper = examPapers.get(i);
+            Paper paper = examPaper.getPaper();
+            paper = Model.of(paperRepo.findById(paper.getId()));
+            examPaper.setPaper(paper);
+        }
+        Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
+        extractConfig.setCourse(course);
+        extractConfig.setCourseName(course.getName());
+        Map<String, String> newFinishedPaperIdMap = makePaperByConfig(extractConfig);
+        extractConfig.setFinishedPaperIdMap(newFinishedPaperIdMap);
+        extractConfig.setIfFinish((short) 1);
+        extractConfigRepo.save(extractConfig);
+        return newFinishedPaperIdMap;
+    }
+
+    @Override
+    public List<ExamPaper> saveExtractConfig(ExtractConfig extractConfig, User user) {
+    	Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
+        extractConfig.setCourse(course);
+        extractConfig.setCourseName(course.getName());
+        List<ExamPaper> examPapers = extractConfig.getExamPaperList();
+        for (int i = 0; i < examPapers.size(); i++) {
+            ExamPaper examPaper = examPapers.get(i);
+            Paper paper = examPaper.getPaper();
+            paper = Model.of(paperRepo.findById(paper.getId()));
+            if(!paper.getCourse().getId().equals(extractConfig.getCourse().getId())) {
+            	throw new StatusException("试卷的课程和当前课程不匹配");
+            }
+            examPaper.setPaper(paper);
+            if (ExamType.OFFLINE.name().equals(extractConfig.getExamType())) {
+                checkOfflinePaper(paper);
+            }
+        }
+        if (StringUtils.isNotBlank(extractConfig.getId())) {
+            disposeOldPaper(extractConfig.getId());
+        }
+        extractConfig.setIfFinish((short) 1);
+        extractConfigRepo.save(extractConfig);
+        disposeNowPaper(extractConfig.getId());
+        return examPapers;
+    }
+    
+    @Transactional
+    @Override
+    public void saveExtractConfigForRandomPaper(ExtractConfig extractConfig) {
+        Course course = courseService.getCourse(Long.valueOf(extractConfig.getOrgId()), extractConfig.getCourseCode());
+        extractConfig.setCourse(course);
+        extractConfig.setCourseName(course.getName());
+
+        RandomPaper query = new RandomPaper();
+        query.setId(extractConfig.getRandomPaperId());
+        query.setCourseId(Long.parseLong(course.getId()));
+        Example<RandomPaper> queryExample = Example.of(query);
+        boolean exists = randomPaperRepo.exists(queryExample);
+        if(!exists){
+            LOG.warn("随机抽题模板选择错误,与当前课程不匹配! courseId:{} randomPaperId:{}", course.getId(), extractConfig.getRandomPaperId());
+            throw new StatusException("500", "随机抽题模板选择错误,与当前课程不匹配!");
+        }
+
+		// 新增,id为空
+		if (StringUtils.isBlank(extractConfig.getId())) {
+			// 先查询
+			ExtractConfig tempConfig = this.findConfig(extractConfig);
+			if (tempConfig != null) {
+				tempConfig.setRandomPaperId(extractConfig.getRandomPaperId());
+				tempConfig.setPlayTime(extractConfig.getPlayTime());
+				tempConfig.setCourseName(course.getName());
+				tempConfig.setCourse(course);
+				extractConfig=tempConfig;
 			}
-		}
-	}
-	/**处理当前绑定试卷
-	 * @param extractConfigId
-	 */
-	private void disposeNowPaper(String extractConfigId) {
-		ExtractConfig ec = Model.of(extractConfigRepo.findById(extractConfigId));
-		for (ExamPaper paper : ec.getExamPaperList()) {
-			updatePaperInUse(paper.getPaper().getId(), 1);
-		}
-	}
-
-	private void updatePaperInUse(String paperId, int inUse) {
-		Query query = null;
-		if(paperId.length()==24) {
-			query = Query.query(Criteria.where("_id").is(new ObjectId(paperId)));
 		}else {
-			query = Query.query(Criteria.where("_id").is(paperId));
-		}
-		Update update = new Update();
-		update.set("inUse", inUse);
-		mongoTemplate.updateFirst(query, update, "paper");
-	}
-
-	private boolean inExam(String paperId) {
-		CheckPaperInExamReq req1 = new CheckPaperInExamReq();
-		req1.setBasePaperId(paperId);
-		CheckPaperInExamResp res1 = adminExamRecordCloudService.checkPaperInExam(req1);
-		if (res1.getInExam()) {
-			return res1.getInExam();
-		}
-		cn.com.qmth.examcloud.core.oe.student.api.request.CheckPaperInExamReq req2 = new cn.com.qmth.examcloud.core.oe.student.api.request.CheckPaperInExamReq();
-		req2.setBasePaperId(paperId);
-		cn.com.qmth.examcloud.core.oe.student.api.response.CheckPaperInExamResp res2 = studentExamRecordCloudService
-				.checkPaperInExam(req2);
-		return res2.getInExam();
-	}
-
-	private boolean inOtherExtractConfig(String extractConfigId, String paperId) {
-		Criteria criteria = new Criteria();
-		criteria.and("examPaperList").elemMatch(Criteria.where("paper.$id").is(paperId));
-		Query query = Query.query(criteria);
-		List<ExtractConfig> list = mongoTemplate.find(query, ExtractConfig.class, "extractConfig");
-		if (list == null || list.size() == 0) {
-			return false;
-		}
-		if (list.size() == 1 && list.get(0).getId().equals(extractConfigId)) {
-			return false;
-		}
-		return true;
-	}
-
-	private void checkOfflinePaper(Paper paper) {
-		List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByPaper(paper);
-		if (pdus == null || pdus.size() == 0) {
-			return;
-		}
-		for (PaperDetailUnit pdu : pdus) {
-			if (pdu.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-					|| pdu.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
-					|| pdu.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
-				throw new StatusException("500", "试卷包含客观题,无法保存规则");
-			} else if (pdu.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION
-					&& !CollectionUtils.isEmpty(pdu.getQuestion().getSubQuestions())) {
-				for (Question q : pdu.getQuestion().getSubQuestions()) {
-					if (q.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-							|| q.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
-							|| q.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
-						throw new StatusException("500", "试卷包含客观题,无法保存规则");
-					}
-				}
-			}
-		}
-	}
-
-	@Override
-	public ExtractConfig findConfigById(String id) {
-		if (StringUtils.isBlank(id)) {
-			return null;
-		}
-		return Model.of(extractConfigRepo.findById(id));
-	}
-
-	@Override
-	public Map<String, Object> extractExamPaper(Long exam_id, String course_code, String group_code) {
-		Map<String, Object> returnMap = new HashMap<>();
-		LOG.info("调卷开始...");
-		long beginTime = System.currentTimeMillis();
-		LOG.info("开始根据examId:" + exam_id + "和courseCode:" + course_code + "获取调卷规则");
-		ExtractConfig extractConfig = this.findConfig(new ExtractConfig(exam_id, course_code));
-		if (extractConfig == null) {
-			LOG.error("该考试和课程下调卷规则未制定,请先制定调卷规则,调卷程序退出");
-			returnMap.put("errorMsg", "该考试和课程下调卷规则未制定,请先制定调卷规则");
-			return returnMap;
-		}
-		long configFinishTime = System.currentTimeMillis();
-		LOG.info("获取调卷规则共耗时:" + (configFinishTime - beginTime) + "ms");
-		LOG.info("根据调卷规则中设置的概率获取类型为" + group_code + "的试卷");
-		Map<String, Paper> paperMap = this.getExamPaperByProbability(extractConfig.getExamPaperList());
-		if (paperMap.isEmpty()) {
-			LOG.error("该考试和课程下调卷规则中试卷不存在,请检查调卷规则,调卷程序退出");
-			returnMap.put("errorMsg", "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则");
-			return returnMap;
-		}
-
-		long paperMapFinishTime = System.currentTimeMillis();
-		LOG.info("获取类型为" + group_code + "的试卷共耗时:" + (paperMapFinishTime - configFinishTime) + "ms");
-
-		Paper basePaper = paperMap.get(group_code);
-		if (basePaper == null) {
-			LOG.error("该考试和课程下调卷规则中该类型试卷不存在,请检查调卷规则,调卷程序退出");
-			returnMap.put("errorMsg", "该考试和课程下调卷规则中该类型试卷不存在,请重新制定调卷规则");
-			return returnMap;
-		}
-		String basePaperId = basePaper.getId();
-		LOG.info("将原始试卷:" + basePaperId + "根据规则重新组卷");
-		int upSetQuestionOrder = extractConfig.getScrambling_the_question_order();
-		int upSetOptionOrder = extractConfig.getScrambling_the_option_order();
-		// 不乱序直接调卷
-		if (upSetQuestionOrder == 0 && upSetOptionOrder == 0) {
-			PaperDto paperDto = getPaperDtoByPaper(basePaper, basePaperId);
-			long paperDtoFinishTime = System.currentTimeMillis();
-			LOG.info("获取试卷Dto共耗时:" + (paperDtoFinishTime - paperMapFinishTime) + "ms");
-			returnMap.put("paperDto", paperDto);
-			LOG.info("调卷完成");
-			LOG.info("总共耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
-		} else {
-			// 乱序重新生成试卷
-			Paper newPaper = this.recombinationPaper(basePaper, PaperType.STUDENT_EXAM, upSetQuestionOrder,
-					upSetOptionOrder);
-			LOG.info("根据新试卷 paperId:" + newPaper.getId() + "组装PaperDto后返回");
-
-			long genPaperFinishTime = System.currentTimeMillis();
-			LOG.info("组卷共耗时:" + (genPaperFinishTime - paperMapFinishTime) + "ms");
-			PaperDto paperDto = getPaperDtoByPaper(newPaper, basePaperId);
-
-			long paperDtoFinishTime = System.currentTimeMillis();
-			LOG.info("获取试卷Dto共耗时:" + (paperDtoFinishTime - genPaperFinishTime) + "ms");
-
-			returnMap.put("paperDto", paperDto);
-			LOG.info("调卷完成");
-			LOG.info("总共耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
-		}
-		return returnMap;
-	}
-
-	@Override
-	public boolean checkIsAllQbjectiveQuestion(String paperId) {
-		// 优先从redis中获取缓存dto
-		PaperDto cachePaperDto = redisClient.get(CACHE_KEY_PAPER_FOR_DTO + paperId, PaperDto.class, DEFAULT_TIME_OUT);
-		if (cachePaperDto != null) {
-			return cachePaperDto.isAllQbjectiveQuestion();
-		}
-
-		Paper paper = Model.of(paperRepo.findById(paperId));
-		List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
-		for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-			Question question = paperDetailUnit.getQuestion();
-			// 填空或问答
-			if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
-					|| question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
-				return false;
-			}
-			if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-				List<Question> subQuestions = question.getSubQuestions();
-				for (Question subQuestion : subQuestions) {
-					if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
-							|| subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
-						return false;
-					}
-				}
-			}
-		}
-		return true;
-	}
-
-	public boolean checkIsAllQbjectiveByPdu(List<PaperDetailUnit> paperDetailUnits) {
-		for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-			Question question = paperDetailUnit.getQuestion();
-			// 填空或问答
-			if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
-					|| question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
-				return false;
-			}
-			if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-				List<Question> subQuestions = question.getSubQuestions();
-				for (Question subQuestion : subQuestions) {
-					if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
-							|| subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
-						return false;
-					}
-				}
-			}
-		}
-		return true;
-	}
-
-	@Override
-	public Map<String, String> makePaperByConfig(ExtractConfig extractConfig) {
-		Map<String, String> finishedPaperIdMap = new HashMap<>();
-		if (extractConfig == null) {
-			throw new RuntimeException("调卷规则不存在");
-		}
-		// 获得规则中设置的试卷
-		Map<String, Paper> paperMap = this.getExamPaperByProbability(extractConfig.getExamPaperList());
-		if (paperMap.isEmpty()) {
-			throw new RuntimeException("抽取试卷失败");
-		}
-		for (Map.Entry<String, Paper> entry : paperMap.entrySet()) {
-			String key = entry.getKey();
-			// 根据原有试卷重新组卷得到新试卷
-			/*
-			 * Paper newPaper = this.recombinationPaper(entry.getValue(), PaperType.PREVIEW,
-			 * extractConfig.getScrambling_the_question_order(),
-			 * extractConfig.getScrambling_the_option_order());
-			 */
-			finishedPaperIdMap.put(key, entry.getValue().getId());
-		}
-		return finishedPaperIdMap;
-	}
-
-	/**
-	 * 重组试卷,生成新的试卷
-	 *
-	 * @param paper              选中的试卷
-	 * @param upSetQuestionOrder 客观题小题乱序 1:乱序 0:不乱序
-	 * @param upSetOptionOrder   客观题选项乱序 1:乱序 0:不乱序
-	 * @return
-	 */
-	public Paper recombinationPaper(Paper paper, PaperType paperType, int upSetQuestionOrder, int upSetOptionOrder) {
-
-		// 将小题全部取出来,只取一次
-		List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
-		// 获取大题
-		List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
-
-		// 抽取大题号对应的小题
-		Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
-				.collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
-		// 最终保存的所有小题
-		List<PaperDetailUnit> savePaperDetailUnits = new ArrayList<>();
-		// 保存试卷信息
-		paper.setId(null);
-		paper.setPaperType(paperType);
-		Paper newPaper = paperRepo.insert(paper);
-
-		for (int i = 0; i < paperDetails.size(); i++) {
-			PaperDetail paperDetail = paperDetails.get(i);
-
-			List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
-			if (paperDetailUnits == null || paperDetailUnits.size() == 0) {
-				continue;
-			}
-			Collections.sort(paperDetailUnits);
-
-			// 将大题中最小的number取出
-			PaperDetailUnit topDetailUnit = paperDetailUnits.get(0);
-			int minNumber = topDetailUnit.getNumber();
-
-			// 小题乱序
-			if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
-				if ((topDetailUnit.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-						|| topDetailUnit.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
-						|| topDetailUnit.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION)
-						&& upSetQuestionOrder == 1) {
-					Collections.shuffle(paperDetailUnits);// 打乱小题List
-				}
-			}
-			// 设置大题信息
-			paperDetail.setId(null);
-			paperDetail.setPaper(newPaper);
-
-			for (int j = 0; j < paperDetailUnits.size(); j++) {
-				// 重新设置保存PaperDetailUnit
-				PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
-				paperDetailUnit.setPaperType(paperType);
-				paperDetailUnit.setPaper(newPaper);
-				paperDetailUnit.setPaperDetail(paperDetail);
-				paperDetailUnit.setNumber(minNumber + j); // 重新设置序号
-				reSavePaperDetailUtilAndQuestion(paperDetailUnit, upSetOptionOrder);
-				savePaperDetailUnits.add(paperDetailUnit);
-			}
-
-		}
-		// 保存大题信息
-		paperDetailRepo.insert(paperDetails);
-		// 保存小题信息
-		paperDetailUnitRepo.insert(savePaperDetailUnits);
-
-		// 清空所有list
-		allPaperDetailUnits.clear();
-		savePaperDetailUnits.clear();
-		paperDetails.clear();
-
-		return newPaper;
-	}
-
-	/**
-	 * 每个试卷类型取出一套试卷 { A:Paper, B:Paper } A是试卷类型 Paper是A类型下选定的试卷
-	 *
-	 * @param examPaperList
-	 * @return
-	 */
-	private Map<String, Paper> getExamPaperByProbability(List<ExamPaper> examPaperList) {
-		Map<String, Paper> paperByTypeMap = new HashMap<>();
-		if (examPaperList == null || examPaperList.size() == 0) {
-			throw new RuntimeException("可供抽取的试卷集合为空,无法抽取试卷");
-		}
-
-		Map<String, List<ExamPaper>> examPaperMap = new HashMap<>();
-		for (int i = 0; i < examPaperList.size(); i++) {
-			ExamPaper examPaper = examPaperList.get(i);
-			if (!examPaperMap.containsKey(examPaper.getGroupCode())) {
-				if (examPaper.getPaper() != null) {
-					List<ExamPaper> epList = new ArrayList<>();
-					epList.add(examPaper);
-					examPaperMap.put(examPaper.getGroupCode(), epList);
-				}
-			} else {
-				if (examPaper.getPaper() != null) {
-					List<ExamPaper> epList = examPaperMap.get(examPaper.getGroupCode());
-					epList.add(examPaper);
-				}
-			}
-		}
-
-		if (examPaperMap != null) {
-			Set<String> keys = examPaperMap.keySet();
-			Iterator<String> it = keys.iterator();
-			while (it.hasNext()) {
-				String key = it.next();
-				Paper paper = this.getPaperByProbability(examPaperMap.get(key));
-
-				if (paper == null) {
-					continue;
-				}
-
-				// 不能用原来的paper对象,否则examPaperList中的paper对象会被覆盖
-				Paper newPaper = Model.of(paperRepo.findById(paper.getId()));
-				paperByTypeMap.put(key, newPaper);
-			}
-		}
-
-		return paperByTypeMap;
-	}
-
-	/**
-	 * 根据设定几率取出一套试卷
-	 *
-	 * @param examPaperList
-	 * @return
-	 */
-	private Paper getPaperByProbability(List<ExamPaper> examPaperList) {
-		int sum = 0;
-		for (int i = 0; i < examPaperList.size(); i++) {
-			sum += examPaperList.get(i).getWeight();
-		}
-
-		// 从1开始
-		Integer rand = new Random().nextInt(sum) + 1;
-		for (int i = 0; i < examPaperList.size(); i++) {
-			rand -= examPaperList.get(i).getWeight();
-			// 选中
-			if (rand <= 0) {
-				return examPaperList.get(i).getPaper();
-			}
-		}
-
-		return null;
-	}
-
-	/**
-	 * 重新设置并保存paperDetailUnit和question
-	 *
-	 * @param paperDetailUnit
-	 * @param upSetOptionOrder
-	 */
-	private void reSavePaperDetailUtilAndQuestion(PaperDetailUnit paperDetailUnit, Integer upSetOptionOrder) {
-		Question question = paperDetailUnit.getQuestion();
-		// 选项乱序
-		if (upSetOptionOrder == 1) {
-			// 单选或多选
-			if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-					|| question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-				List<String> numberList = new ArrayList<>();
-				List<QuesOption> options = question.getQuesOptions();
-				for (int k = 0; k < options.size(); k++) {
-					QuesOption quesOption = options.get(k);
-					numberList.add(quesOption.getNumber());
-				}
-				Collections.shuffle(numberList); // 打乱number顺序
-				paperDetailUnit.setOptionOrder(StringUtils.join(numberList.toArray(), ","));// 设置option顺序
-			}
-			// 套题,套题下小题不乱序,选择题选项乱序
-			if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-				List<Question> subQuestions = question.getSubQuestions();
-				StringBuffer optionOrder = new StringBuffer();
-				for (int m = 0; m < subQuestions.size(); m++) {
-					Question subQuestion = subQuestions.get(m);
-					if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-							|| subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-						List<String> numberList = new ArrayList<>();
-						List<QuesOption> options = subQuestion.getQuesOptions();
-						for (int n = 0; n < options.size(); n++) {
-							QuesOption quesOption = options.get(n);
-							numberList.add(quesOption.getNumber());
-						}
-						Collections.shuffle(numberList); // 打乱number顺序
-						optionOrder.append(StringUtils.join(numberList.toArray(), ",")).append(";");
-					}
-				}
-				paperDetailUnit.setOptionOrder(optionOrder.toString()); // 设置option顺序
-			}
-		}
-		paperDetailUnit.setId(null);
-	}
-
-	@Override
-	public List<String> getExamPaperId(String courseCode, String orgId) {
-		Assert.hasLength(courseCode, "courseCode不能为空");
-		Assert.hasLength(orgId, "orgId不能为空");
-		List<String> paperIdList = new ArrayList<>();
-		Query query = new Query();
-		query.addCriteria(Criteria.where("courseCode").is(courseCode));
-		query.addCriteria(Criteria.where("orgId").is(orgId));
-		List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
-		for (ExtractConfig extractConfig : extractConfigList) {
-			List<ExamPaper> examPaperList = extractConfig.getExamPaperList();
-			if (examPaperList != null && examPaperList.size() > 0) {
-				for (ExamPaper examPaper : examPaperList) {
-					paperIdList.add(examPaper.getPaper().getId());
-				}
-			}
-		}
-		return paperIdList;
-	}
-
-	/**
-	 * 根据paper得到PaperDto
-	 *
-	 * @param paper
-	 * @return
-	 */
-	@SuppressWarnings("unchecked")
-	private PaperDto getPaperDtoByPaper(Paper paper, String basePaperId) {
-		long beginTime = System.currentTimeMillis();
-		// 没有则重新组装
-		PaperDto paperDto = paperDtoAssembler.toDto(paper);
-		paperDto.setBasePaperId(basePaperId);
-		paperDto.setAllQbjectiveQuestion(checkIsAllQbjectiveQuestion(basePaperId));
-
-		long paperDtoEndTime = System.currentTimeMillis();
-		LOG.info("单独组装paperDto耗时:" + (paperDtoEndTime - beginTime) + "ms");
-
-		// 将小题全部取出来,只取一次
-		List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaper(paper);
-		long pduEndTime = System.currentTimeMillis();
-		LOG.info("数据库取小题耗时:" + (pduEndTime - paperDtoEndTime) + "ms");
-		Collections.sort(allPaperDetailUnits);
-		long pduSortEndTime = System.currentTimeMillis();
-		LOG.info("排序小题耗时:" + (pduSortEndTime - pduEndTime) + "ms");
-
-		// 获取大题
-		List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(paper);
-		long pdEndTime = System.currentTimeMillis();
-		LOG.info("数据库取大题耗时:" + (pdEndTime - pduSortEndTime) + "ms");
-		Collections.sort(paperDetails);
-		long pdSortEndTime = System.currentTimeMillis();
-		LOG.info("排序大题耗时:" + (pdSortEndTime - pdEndTime) + "ms");
-
-		// 抽取大题Id对应的小题
-		Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
-				.collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
-		long pduMapEndTime = System.currentTimeMillis();
-		LOG.info("获取大题与小题对应关系耗时:" + (pduMapEndTime - pdSortEndTime) + "ms");
-
-		// 获取大题Dto
-		List<PaperDetailDto> paperDetailDtos = paperDetailDtoAssembler.toDtoList(paperDetails);
-		paperDto.setPaperDetails(paperDetailDtos);
-		long paperDetailDtoEndTime = System.currentTimeMillis();
-		LOG.info("单独组装paperDetailDto耗时:" + (paperDetailDtoEndTime - pduMapEndTime) + "ms");
-
-		// 封装小题
-		for (int i = 0; i < paperDetailDtos.size(); i++) {
-			// 根据大题查出大题下面的小题
-			PaperDetail paperDetail = paperDetails.get(i);
-
-			List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
-
-			List<PaperDetailUnitDto> paperDetailUnitDtos = new ArrayList<>();
-			for (int j = 0; j < paperDetailUnits.size(); j++) {
-				PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
-				if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
-					break;
-				}
-				// 设置答案
-				setSelectQuestionAnswerUnit(paperDetailUnit);
-
-				PaperDetailUnitDto unitDto = paperDetailUnitDtoAssembler.toDto(paperDetailUnit);
-				/**
-				 * 此处不能传questionId,需要传paperDetailUnitId 因为选项乱序在paperDetailUnit里
-				 * unitDto.setQuesId(paperDetailUnit.getQuestion().getId());
-				 */
-				unitDto.setQuesId(paperDetailUnit.getId());
-				String answer = paperDetailUnit.getQuestion().getQuesAnswer();
-				if (StringUtils.isNotEmpty(answer)) {
-					unitDto.setAnswer(answer);
-				}
-				if (unitDto.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {// 假如是套题
-					List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
-					List<SubQuestionDto> subQuesDtos = subQuestionDtoAssembler.toDtoList(subQuesList);
-					for (int m = 0; m < subQuesList.size(); m++) {
-						List<QuesOptionDto> quesOptionDtos = subQuestionDtoAssembler
-								.toOptionDtoList(subQuesList.get(m).getQuesOptions());
-						subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
-						if (StringUtils.isNotEmpty(subQuesList.get(m).getQuesAnswer())) {
-							subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
-						}
-						subQuesDtos.get(m).setNumber(m + 1);
-						// 套题分数从小题类中取值
-						subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
-					}
-					unitDto.setSubQuestions(subQuesDtos);
-				}
-				paperDetailUnitDtos.add(unitDto);
-			}
-			paperDetailDtos.get(i).setPaperDetailUnits(paperDetailUnitDtos);
-			paperDetailDtos.get(i).setCnNum(CommonUtils.toCHNum(paperDetailDtos.get(i).getNumber()));
-		}
-		long paperDetailUnitDtoEndTime = System.currentTimeMillis();
-		LOG.info("单独组装paperDetailUnitDto耗时:" + (paperDetailUnitDtoEndTime - paperDetailDtoEndTime) + "ms");
-
-		// 将重新组装的dto放进缓存
-		redisClient.set(CACHE_KEY_PAPER_FOR_DTO + paperDto.getId(), paperDto, DEFAULT_TIME_OUT);
-		return paperDto;
-	}
-
-	/**
-	 * 测试获取paperDto
-	 *
-	 * @param basePaperId
-	 * @return
-	 */
-	@Deprecated
-	public PaperDto getPaperDtoByPaperNew(String basePaperId) {
-
-		long beginTime = System.currentTimeMillis();
-		// 没有则重新组装
-		Paper paper = Model.of(paperRepo.findById(basePaperId));
-		PaperDto paperDto = paperDtoAssembler.toDto(paper);
-		paperDto.setBasePaperId(basePaperId);
-
-		long paperDtoEndTime = System.currentTimeMillis();
-		LOG.info("单独组装paperDto耗时:" + (paperDtoEndTime - beginTime) + "ms");
-
-		// 将小题全部取出来,只取一次
-		// List<PaperDetailUnit> allPaperDetailUnits =
-		// detailUnitNativeRepo.findByPaperId(paper.getId());
-		List<PaperDetailUnit> allPaperDetailUnits = new ArrayList<>();
-
-		long pduEndTime = System.currentTimeMillis();
-		LOG.info("数据库取小题耗时:" + (pduEndTime - paperDtoEndTime) + "ms");
-		Collections.sort(allPaperDetailUnits);
-		long pduSortEndTime = System.currentTimeMillis();
-		LOG.info("排序小题耗时:" + (pduSortEndTime - pduEndTime) + "ms");
-
-		paperDto.setAllQbjectiveQuestion(checkIsAllQbjectiveByPdu(allPaperDetailUnits));
-		long isAllObjEndtime = System.currentTimeMillis();
-		LOG.info("设置客观题耗时:" + (isAllObjEndtime - pduSortEndTime) + "ms");
-
-		// 获取大题
-		List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(paper);
-		long pdEndTime = System.currentTimeMillis();
-		LOG.info("数据库取大题耗时:" + (pdEndTime - isAllObjEndtime) + "ms");
-		Collections.sort(paperDetails);
-		long pdSortEndTime = System.currentTimeMillis();
-		LOG.info("排序大题耗时:" + (pdSortEndTime - pdEndTime) + "ms");
-
-		// 抽取大题Id对应的小题
-		Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
-				.collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
-		long pduMapEndTime = System.currentTimeMillis();
-		LOG.info("获取大题与小题对应关系耗时:" + (pduMapEndTime - pdSortEndTime) + "ms");
-
-		// 获取大题Dto
-		List<PaperDetailDto> paperDetailDtos = paperDetailDtoAssembler.toDtoList(paperDetails);
-		paperDto.setPaperDetails(paperDetailDtos);
-		long paperDetailDtoEndTime = System.currentTimeMillis();
-		LOG.info("单独组装paperDetailDto耗时:" + (paperDetailDtoEndTime - pduMapEndTime) + "ms");
-
-		// 封装小题
-		for (int i = 0; i < paperDetailDtos.size(); i++) {
-			// 根据大题查出大题下面的小题
-			PaperDetail paperDetail = paperDetails.get(i);
-
-			List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
-
-			List<PaperDetailUnitDto> paperDetailUnitDtos = new ArrayList<>();
-			for (int j = 0; j < paperDetailUnits.size(); j++) {
-				PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
-				if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
-					break;
-				}
-				// 设置答案
-				setSelectQuestionAnswerUnit(paperDetailUnit);
-
-				PaperDetailUnitDto unitDto = paperDetailUnitDtoAssembler.toDto(paperDetailUnit);
-				/**
-				 * 此处不能传questionId,需要传paperDetailUnitId 因为选项乱序在paperDetailUnit里
-				 * unitDto.setQuesId(paperDetailUnit.getQuestion().getId());
-				 */
-				unitDto.setQuesId(paperDetailUnit.getId());
-				String answer = paperDetailUnit.getQuestion().getQuesAnswer();
-				if (StringUtils.isNotEmpty(answer)) {
-					unitDto.setAnswer(answer);
-				}
-				if (unitDto.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {// 假如是套题
-					List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
-					List<SubQuestionDto> subQuesDtos = subQuestionDtoAssembler.toDtoList(subQuesList);
-					for (int m = 0; m < subQuesList.size(); m++) {
-						List<QuesOptionDto> quesOptionDtos = subQuestionDtoAssembler
-								.toOptionDtoList(subQuesList.get(m).getQuesOptions());
-						subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
-						if (StringUtils.isNotEmpty(subQuesList.get(m).getQuesAnswer())) {
-							subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
-						}
-						subQuesDtos.get(m).setNumber(m + 1);
-						// 套题分数从小题类中取值
-						subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
-					}
-					unitDto.setSubQuestions(subQuesDtos);
-				}
-				paperDetailUnitDtos.add(unitDto);
-			}
-			paperDetailDtos.get(i).setPaperDetailUnits(paperDetailUnitDtos);
-			paperDetailDtos.get(i).setCnNum(CommonUtils.toCHNum(paperDetailDtos.get(i).getNumber()));
-		}
-		long paperDetailUnitDtoEndTime = System.currentTimeMillis();
-		LOG.info("单独组装paperDetailUnitDto耗时:" + (paperDetailUnitDtoEndTime - paperDetailDtoEndTime) + "ms");
-		return paperDto;
-	}
-
-	private void setSelectQuestoionAnswer(List<PaperDetailUnit> paperDetailUnits) {
-		for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-			if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
-				break;
-			}
-			String optionOrder = paperDetailUnit.getOptionOrder();
-			Question question = paperDetailUnit.getQuestion();
-			quesService.setSelectQuestionAnswer(question, optionOrder);
-		}
-	}
-
-	private void setSelectQuestionAnswerUnit(PaperDetailUnit paperDetailUnit) {
-		Question question = paperDetailUnit.getQuestion();
-		if (paperDetailUnit == null || question == null) {
-			return;
-		}
-		String optionOrder = paperDetailUnit.getOptionOrder();
-		quesService.setSelectQuestionAnswer(question, optionOrder);
-	}
-
-	/**
-	 * 根据paperDetailUnitId抽取单个试题 根据paperDetailUnitId中设置的option顺序对option排序
-	 */
-	@Override
-	public QuestionDto extractExamQuestion(String examId, String courseCode, String groupCode,
-			String paperDetailUnitId) {
-		PaperDetailUnit paperDetailUnit = Model.of(paperDetailUnitRepo.findById(paperDetailUnitId));
-		List<PaperDetailUnit> paperDetailUnits = new ArrayList<>();
-		paperDetailUnits.add(paperDetailUnit);
-		// 设置答案
-		setSelectQuestoionAnswer(paperDetailUnits);
-		// 重新对选择题option进行排序(多选、单选、套题下选择题)
-		reorderChoicequestionOption(paperDetailUnit);
-		Question ques = paperDetailUnit.getQuestion();
-		quesService.formatQues(ques);
-		QuestionDto dto = BeanCopierUtil.copyProperties(ques, QuestionDto.class);
-		dto.setScore(paperDetailUnit.getScore());
-		dto.setQuesOptions(buildQuestionOptionDto(ques.getQuesOptions()));
-		if (ques.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-			List<Question> subQuesList = ques.getSubQuestions();
-			List<SubQuestionDto> subQuesDtos = new ArrayList<>();
-			for (Question question : subQuesList) {
-				SubQuestionDto subQuestionDto = new SubQuestionDto();
-				BeanUtils.copyProperties(question, subQuestionDto);
-				subQuesDtos.add(subQuestionDto);
-			}
-			for (int m = 0; m < subQuesList.size(); m++) {
-				List<QuesOptionDto> quesOptionDtos = BeanCopierUtil
-						.copyPropertiesOfList(subQuesList.get(m).getQuesOptions(), QuesOptionDto.class);
-				subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
-				subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
-				subQuesDtos.get(m).setNumber(m + 1);
-				// 套题分数从小题类中取值
-				subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
-				dto.setSubQuestions(subQuesDtos);
-			}
-		}
-		if (ques.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-				|| ques.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-			dto.setQuesAnswer(ques.getQuesAnswer());
-		}
-		appendAudioFlag(dto, examId, courseCode, groupCode);
-		return dto;
-	}
-
-	private List<QuesOptionDto> buildQuestionOptionDto(List<QuesOption> quesOptions) {
-		if (quesOptions == null) {
-			return null;
-		}
-		List<QuesOptionDto> optionDtos = new ArrayList<>();
-		for (QuesOption option : quesOptions) {
-			QuesOptionDto optionDto = new QuesOptionDto();
-			optionDto.setNumber(option.getNumber());
-			optionDto.setOptionBody(option.getOptionBody());
-			optionDtos.add(optionDto);
-		}
-		return optionDtos;
-	}
-
-	/**
-	 * 1.给QuestionDto添加音频播放次数 2.给试题音频中有a标签添加url
-	 *
-	 * @param questionDto
-	 */
-	private void appendAudioFlag(QuestionDto questionDto, String examId, String courseCode, String groupCode) {
-		if (questionDto.getHasAudio() != null && questionDto.getHasAudio() == true) {
-			// 1.判断questionDto是否含有音频,如果有添加音频播放次数
-			AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo
-					.findOne(Example.of(new AudioTimeConfig(examId, courseCode, groupCode, questionDto.getId()))));
-			questionDto.setPlayTime(audioTimeConfig != null ? audioTimeConfig.getPlayTime() : null);
-			// 2.1 取到题干,给a标签添加url
-			String quesBody = questionDto.getQuesBody();
-			questionDto.setQuesBody(buildBody(quesBody, questionDto));
-			// 2.2取到选项,给a标签添加url
-			List<QuesOptionDto> quesOptionDtoList = questionDto.getQuesOptions();
-			if (quesOptionDtoList != null && quesOptionDtoList.size() > 0) {
-				for (QuesOptionDto quesOptionDto : quesOptionDtoList) {
-					quesOptionDto.setOptionBody(buildBody(quesOptionDto.getOptionBody(), questionDto));
-				}
-			}
-		} else {
-			questionDto.setPlayTime(null);
-		}
-	}
-
-	// 给题目和选项添加url
-	public String buildBody(String body, QuestionDto questionDto) {
-		String[] bodyStrings = body.split("></a>");
-		if (bodyStrings.length > 1) {
-			String resultBody = "";
-			for (int i = 0; i < bodyStrings.length; i++) {
-				String containAStr = bodyStrings[i];
-				if (containAStr.indexOf("<a") > -1) {
-					String questionAudioId = matchAudioName(containAStr, "a", "id");
-					QuestionAudio questionAudio = questionAudioService.findAudioById(questionAudioId);
-					if (questionAudio != null) {
-//                        String url = sysProperty.getDomain() + questionAudio.getFileUrl();
-						// 通用存储
-						String url = FileStorageUtil.realPath(questionAudio.getFileUrl());
-						if (questionDto.getPlayTime() != null) {
-							containAStr += " question-audio url=\"" + url + "\" playTime=\"" + questionDto.getPlayTime()
-									+ "\"" + "></a>";
-						} else {
-							containAStr += " question-audio url=\"" + url + "\"" + "></a>";
-						}
-					}
-				}
-				resultBody += containAStr;
-			}
-			return resultBody;
-		} else {
-			return body;
-		}
-	}
-
-	private String matchAudioName(String source, String element, String attr) {
-		String reg = "<" + element + "[^<>]*?\\s" + attr + "=['\"]?(.*?)['\"]?(\\s.*?)";
-		Matcher m = Pattern.compile(reg).matcher(source);
-		if (m.find()) {
-			return m.group(1);
-		}
-		return "";
-	}
-
-	/**
-	 * 重新对选择题option进行排序(多选、单选、套题下选择题)
-	 */
-	private void reorderChoicequestionOption(PaperDetailUnit paperDetailUnit) {
-		String optionOrder = paperDetailUnit.getOptionOrder();
-		if (StringUtil.isNotBlank(optionOrder)) {
-			Question question = paperDetailUnit.getQuestion();
-			if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-					|| question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-				question.setQuesOptions(reorderOptionCore(question.getQuesOptions(), optionOrder));
-			}
-			if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-				List<Question> subQuestions = question.getSubQuestions();
-				int index = 0;
-				for (int k = 0; k < subQuestions.size(); k++) {
-					Question subQuestion = subQuestions.get(k);
-					if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-							|| subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-						subQuestion.setQuesOptions(
-								reorderOptionCore(subQuestion.getQuesOptions(), optionOrder.split(";")[index]));
-						index++;
-					}
-				}
-			}
-		}
-	}
-
-	/**
-	 * 对option排序
-	 *
-	 * @param quesOptions
-	 * @param optionOrder
-	 * @return
-	 */
-	private List<QuesOption> reorderOptionCore(List<QuesOption> quesOptions, String optionOrder) {
-		List<QuesOption> newQuesOptions = new ArrayList<>();
-		if (StringUtil.isBlank(optionOrder) || quesOptions.isEmpty()) {
-			return null;
-		}
-		String[] optionOrderArr = optionOrder.split(",");
-		for (int j = 0; j < optionOrderArr.length; j++) {
-			for (int k = 0; k < quesOptions.size(); k++) {
-				if (optionOrderArr[j].equals(quesOptions.get(k).getNumber())) {
-					newQuesOptions.add(quesOptions.get(k));
-				}
-			}
-		}
-		quesOptions = null;
-		return newQuesOptions;
-	}
-
-	@Override
-	public Page<ExtractConfig> findPageExtractConfig(int currentPage, int pageSize, Long examId, String courseCode,
-			String orgId, UserDataRule ud) {
-		if (ud.assertEmptyQueryResult()) {
-			return Page.empty();
-		}
-		if (examId == null) {
-			throw new StatusException("Q-", "examId is null");
-		}
-		Query query = new Query();
-		query.addCriteria(Criteria.where("orgId").is(orgId));
-		if (ud.assertNeedQueryRefIds()) {
-			query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
-		}
-		query.addCriteria(Criteria.where("examId").is(examId));
-		query.addCriteria(Criteria.where("course.enable").is("true"));
-		if (!StringUtils.isBlank(courseCode)) {
-			query.addCriteria(Criteria.where("course.code").is(courseCode));
-		}
-
-		long count = this.mongoTemplate.count(query, ExtractConfig.class);
-		query.limit(pageSize);
-		query.skip((currentPage - 1L) * pageSize);
-
-		List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
-		if (extractConfigList != null && extractConfigList.size() > 0) {
-			// 调用考务rmi,获取考试信息
-			GetExamReq req = new GetExamReq();
-			req.setId(examId);
-			req.setRootOrgId(Long.valueOf(orgId));
-			GetExamResp resp = examCloudService.getExam(req);
-			ExamBean bean = resp.getExamBean();
-			for (ExtractConfig extractConfig : extractConfigList) {
-				extractConfig.setExamType(bean.getExamType());
-			}
-		}
-
-		return new PageImpl<>(extractConfigList, PageRequest.of(currentPage - 1, pageSize), count);
-	}
-
-	@Override
-	public Map<String, Object> extractPaper(String paperId) {
-		Map<String, Object> returnMap = new HashMap<>();
-		Paper paper = Model.of(paperRepo.findById(paperId));
-		if (paper == null) {
-			returnMap.put("errorMsg", "该试卷不存在");
-			return returnMap;
-		}
-		PaperDto paperDto = getPaperDtoByPaper(paper, paperId);
-		returnMap.put("paperDto", paperDto);
-		return returnMap;
-	}
-
-	@Override
-	public String getAnswerHtml(String paperId) {
-		// 1.根据id查询试卷
-		Paper paper = Model.of(paperRepo.findById(paperId));
-		// 2.定义html字符串
-		StringBuffer answerHtml = new StringBuffer("<p style=\"text-align:center;font-size:20px;font-weight:bold\">"
-				+ paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")" + "</p>");
-		// 添加考试说明
-		if (paper.getExamRemark() == null) {
-			answerHtml.append("<p>考试说明:</p>");
-		} else {
-			answerHtml.append("<p>考试说明:</p>" + paper.getExamRemark());
-		}
-		// 3.通过试卷获取考试端试卷结构
-		PaperDto paperDto = getPaperDtoByPaper(paper, paper.getId());
-		// 4.获取试卷所有大题,并且循环
-		List<PaperDetailDto> paperDetailDtos = paperDto.getPaperDetails();
-		for (PaperDetailDto paperDetailDto : paperDetailDtos) {
-			String title = "<p>" + paperDetailDto.getCnNum() + "、" + paperDetailDto.getName() + "(共"
-					+ paperDetailDto.getScore() + "分)" + "</p>";
-			answerHtml.append(title);
-			// 5.获取大题下面的小题,并循环
-			List<PaperDetailUnitDto> paperDetailUnitDtos = paperDetailDto.getPaperDetailUnits();
-			for (PaperDetailUnitDto paperDetailUnitDto : paperDetailUnitDtos) {
-				// 定义题干
-				String body = paperDetailUnitDto.getQuesBody().replaceAll("<span>", "").replaceAll("</span>", "")
-						.replaceAll("###", "___");
-				;
-				// 定义选项
-				String option = "";
-				// 定义答案
-				String answer = "";
-				// 6.如果为单选和多选
-				if (paperDetailUnitDto.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-						|| paperDetailUnitDto.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-					// 获取选项
-					List<QuesOptionDto> quesOptionDtos = paperDetailUnitDto.getQuesOptions();
-					// 添加题干
-					body = startWithP(body, paperDetailUnitDto.getNumber(), false);
-					answerHtml.append(body);
-					// 添加选项
-					for (QuesOptionDto quesOptionDto : quesOptionDtos) {
-						option = startWithP(quesOptionDto.getOptionBody(), Integer.parseInt(quesOptionDto.getNumber()),
-								true);
-						answerHtml.append(option);
-					}
-					answer = startWithP(paperDetailUnitDto.getAnswer(), -1, false);
-					answerHtml.append(answer);
-				}
-				// 7.如果为套题
-				else if (paperDetailUnitDto.getSubQuestions() != null
-						&& paperDetailUnitDto.getSubQuestions().size() > 0) {
-					// 添加主题干
-					String bigBody = CommonUtils.relaceQuestionIdx(paperDetailUnitDto.getQuesBody(), 0);
-					bigBody = bigBody.replaceAll("<span>", "").replaceAll("</span>", "").replaceAll("###", "___");
-					bigBody = startWithP(bigBody, -2, false);
-					answerHtml.append(bigBody);
-					List<SubQuestionDto> subQuestionDtos = paperDetailUnitDto.getSubQuestions();
-					for (SubQuestionDto subQuestionDto : subQuestionDtos) {
-						body = subQuestionDto.getQuesBody().replaceAll("<span>", "").replaceAll("</span>", "")
-								.replaceAll("###", "___");
-						// 如果子题为单选或者多选
-						if (subQuestionDto.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-								|| subQuestionDto.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-							// 获取选项
-							List<QuesOptionDto> quesOptionDtos = subQuestionDto.getQuesOptions();
-							// 添加题干
-							body = startWithP(body, subQuestionDto.getNumber(), false);
-							answerHtml.append(body);
-							// 添加选项
-							for (QuesOptionDto quesOptionDto : quesOptionDtos) {
-								option = startWithP(quesOptionDto.getOptionBody(),
-										Integer.parseInt(quesOptionDto.getNumber()), true);
-								answerHtml.append(option);
-							}
-							answer = startWithP(subQuestionDto.getQuesAnswer(), -1, false);
-							answerHtml.append(answer);
-						} else {
-							// 添加题干
-							body = startWithP(body, subQuestionDto.getNumber(), false);
-							answerHtml.append(body);
-							// 添加答案
-							answer = startWithP(subQuestionDto.getQuesAnswer(), -1, false);
-							answerHtml.append(answer);
-						}
-					}
-				} else {
-					// 添加题干
-					body = startWithP(body, paperDetailUnitDto.getNumber(), false);
-					answerHtml.append(body);
-					// 添加答案
-					answer = startWithP(paperDetailUnitDto.getAnswer(), -1, false);
-					answerHtml.append(answer);
-				}
-			}
-		}
-		return answerHtml.toString();
-	}
-
-	public String startWithP(String s, Integer number, boolean option) {
-		if (s == null) {
-			s = "<p></p>";
-		}
-		if (number == -2) {
-			if (s.startsWith("<p>")) {
-				s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceAll("<p style=\"margin-left:40px;\">",
-						"<p style=\"margin-left:20px;\">");
-			} else {
-				s = "<p style=\"margin-left:20px;color:red;\">" + s + "</p>";
-			}
-		} else if (number == -1) {
-			if (s.startsWith("<p>")) {
-				s = s.replaceAll("<p>", "<p style=\"margin-left:65px;\">")
-						.replaceFirst("<p style=\"margin-left:65px;\">",
-								"<p style=\"margin-left:20px;color:red;\">" + "答案:")
-						.replace("<p style=\"margin-left:65px;\">", "<p style=\"margin-left:65px;color:red;\">");
-			} else {
-				s = "<p style=\"margin-left:20px;color:red;\">" + "答案:" + s + "</p>";
-			}
-		} else {
-			if (option) {
-				if (s.startsWith("<p>")) {
-					s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceFirst(
-							"<p style=\"margin-left:40px;\">",
-							"<p style=\"margin-left:20px;\">" + CommonUtils.getOptionNum(number - 1) + ".");
-				} else {
-					s = "<p style=\"margin-left:20px;\">" + CommonUtils.getOptionNum(number - 1) + "." + s + "</p>";
-				}
-			} else {
-				if (s.startsWith("<p>")) {
-					s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceFirst(
-							"<p style=\"margin-left:40px;\">", "<p style=\"margin-left:20px;\">" + number + ".");
-				} else {
-					s = "<p style=\"margin-left:20px;\">" + number + "." + s + "</p>";
-				}
-			}
-		}
-		return s;
-	}
-
-	@Override
-	public List<CouresInfo> findCourseByExtractConfig(Long examId, String orgId, UserDataRule ud) {
-		if (ud.assertEmptyQueryResult()) {
-			return new ArrayList<>();
-		}
-		if (examId == null) {
-			throw new StatusException("500", "examId is null");
-		}
-		// 从考务查询改考试下的所有开启课程
-		List<CouresInfo> courseInfoList = new ArrayList<>();
-		GetExamCourseListReq req = new GetExamCourseListReq();
-		req.setExamId(examId);
-		req.setCourseEnable(true);
-		Long start = 1l;
-		int count = 0;
-		while (true) {
-			req.setStart(start);
-			GetExamCourseListResp resp = examCloudService.getExamCourseList(req);
-			for (ExamCourseRelationBean bean : resp.getRelationList()) {
-				CouresInfo info = new CouresInfo(bean);
-				courseInfoList.add(info);
-			}
-			if (start.equals(resp.getNext())) {
-				break;
-			} else {
-				start = resp.getNext();
-			}
-			count++;
-			if (count > 1000) {
-				throw new StatusException("Q-", "考试下课程的数据量过大");
-			}
-		}
-		if (CollectionUtils.isEmpty(courseInfoList)) {
-			return null;
-		}
-		// 查询已经改考试下已经制定的课程的调卷规则
-		Query query = new Query();
-		query.addCriteria(Criteria.where("orgId").is(orgId));
-		query.addCriteria(Criteria.where("examId").is(examId));
-		List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
-		if (extractConfigList != null && extractConfigList.size() > 0) {
-			for (ExtractConfig extractConfig : extractConfigList) {
-				CouresInfo info = new CouresInfo();
-				info.setCourseCode(extractConfig.getCourseCode());
-				if (courseInfoList.contains(info)) {
-					courseInfoList.remove(info);
-				}
-			}
-		}
-		if (ud.assertNeedQueryRefIds() && courseInfoList.size() > 0) {
-			List<CouresInfo> ret = new ArrayList<>();
-			for (CouresInfo info : courseInfoList) {
-				if (ud.getRefIds().contains(info.getCourseId())) {
-					ret.add(info);
-				}
-			}
-			return ret;
-		}
-		return courseInfoList;
-	}
-
-	@Override
-	public List<ExtractConfig> findExtractConfig(Long examId) {
-		if (examId == null) {
-			throw new StatusException("1001", "examId is null");
-		}
-		Query query = new Query();
-		query.addCriteria(Criteria.where("examId").is(examId));
-		query.addCriteria(Criteria.where("course.enable").is("true"));
-
-		List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
-
-		return extractConfigList;
-	}
+			ExtractConfig tempConfig = Model.of(extractConfigRepo.findById(extractConfig.getId()));
+			tempConfig.setRandomPaperId(extractConfig.getRandomPaperId());
+			tempConfig.setPlayTime(extractConfig.getPlayTime());
+			tempConfig.setCourseName(course.getName());
+			tempConfig.setCourse(course);
+			extractConfig=tempConfig;
+		}
+        extractConfigRepo.save(extractConfig);
+    }
+
+    /**
+     * 先处理原来绑定的试卷
+     *
+     * @param extractConfigId
+     */
+    private void disposeOldPaper(String extractConfigId) {
+        ExtractConfig ec = Model.of(extractConfigRepo.findById(extractConfigId));
+        for (ExamPaper paper : ec.getExamPaperList()) {
+            if (inExam(paper.getPaper().getId())) {//如果有考试记录
+                updatePaperInUse(paper.getPaper().getId(), 1);
+            } else {
+                if (!inOtherExtractConfig(extractConfigId, paper.getPaper().getId())) {//没有考试记录且不被其他调卷规则引用
+                    updatePaperInUse(paper.getPaper().getId(), 0);
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理当前绑定试卷
+     *
+     * @param extractConfigId
+     */
+    private void disposeNowPaper(String extractConfigId) {
+        ExtractConfig ec = Model.of(extractConfigRepo.findById(extractConfigId));
+        for (ExamPaper paper : ec.getExamPaperList()) {
+            updatePaperInUse(paper.getPaper().getId(), 1);
+        }
+    }
+
+    private void updatePaperInUse(String paperId, int inUse) {
+        Query query = null;
+        if (paperId.length() == 24) {
+            query = Query.query(Criteria.where("_id").is(new ObjectId(paperId)));
+        } else {
+            query = Query.query(Criteria.where("_id").is(paperId));
+        }
+        Update update = new Update();
+        update.set("inUse", inUse);
+        mongoTemplate.updateFirst(query, update, "paper");
+    }
+
+    private boolean inExam(String paperId) {
+        CheckPaperInExamReq req1 = new CheckPaperInExamReq();
+        req1.setBasePaperId(paperId);
+        CheckPaperInExamResp res1 = adminExamRecordCloudService.checkPaperInExam(req1);
+        if (res1.getInExam()) {
+            return res1.getInExam();
+        }
+        cn.com.qmth.examcloud.core.oe.student.api.request.CheckPaperInExamReq req2 = new cn.com.qmth.examcloud.core.oe.student.api.request.CheckPaperInExamReq();
+        req2.setBasePaperId(paperId);
+        cn.com.qmth.examcloud.core.oe.student.api.response.CheckPaperInExamResp res2 = studentExamRecordCloudService
+                .checkPaperInExam(req2);
+        return res2.getInExam();
+    }
+
+    private boolean inOtherExtractConfig(String extractConfigId, String paperId) {
+        Criteria criteria = new Criteria();
+        criteria.and("examPaperList").elemMatch(Criteria.where("paper.$id").is(paperId));
+        Query query = Query.query(criteria);
+        List<ExtractConfig> list = mongoTemplate.find(query, ExtractConfig.class, "extractConfig");
+        if (list == null || list.size() == 0) {
+            return false;
+        }
+        if (list.size() == 1 && list.get(0).getId().equals(extractConfigId)) {
+            return false;
+        }
+        return true;
+    }
+
+    private void checkOfflinePaper(Paper paper) {
+        List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByPaper(paper);
+        if (pdus == null || pdus.size() == 0) {
+            return;
+        }
+        for (PaperDetailUnit pdu : pdus) {
+            if (pdu.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                    || pdu.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
+                    || pdu.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+                throw new StatusException("500", "试卷包含客观题,无法保存规则");
+            } else if (pdu.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION
+                    && !CollectionUtils.isEmpty(pdu.getQuestion().getSubQuestions())) {
+                for (Question q : pdu.getQuestion().getSubQuestions()) {
+                    if (q.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                            || q.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
+                            || q.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION) {
+                        throw new StatusException("500", "试卷包含客观题,无法保存规则");
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public ExtractConfig findConfigById(String id) {
+        if (StringUtils.isBlank(id)) {
+            return null;
+        }
+        return Model.of(extractConfigRepo.findById(id));
+    }
+
+    @Override
+    public Map<String, Object> extractExamPaper(Long exam_id, String course_code, String group_code) {
+        Map<String, Object> returnMap = new HashMap<>();
+        LOG.info("调卷开始...");
+        long beginTime = System.currentTimeMillis();
+        LOG.info("开始根据examId:" + exam_id + "和courseCode:" + course_code + "获取调卷规则");
+        ExtractConfig extractConfig = this.findConfig(new ExtractConfig(exam_id, course_code));
+        if (extractConfig == null) {
+            LOG.error("该考试和课程下调卷规则未制定,请先制定调卷规则,调卷程序退出");
+            returnMap.put("errorMsg", "该考试和课程下调卷规则未制定,请先制定调卷规则");
+            return returnMap;
+        }
+        long configFinishTime = System.currentTimeMillis();
+        LOG.info("获取调卷规则共耗时:" + (configFinishTime - beginTime) + "ms");
+        LOG.info("根据调卷规则中设置的概率获取类型为" + group_code + "的试卷");
+        Map<String, Paper> paperMap = this.getExamPaperByProbability(extractConfig.getExamPaperList());
+        if (paperMap.isEmpty()) {
+            LOG.error("该考试和课程下调卷规则中试卷不存在,请检查调卷规则,调卷程序退出");
+            returnMap.put("errorMsg", "该考试和课程下调卷规则中试卷不存在,请重新制定调卷规则");
+            return returnMap;
+        }
+
+        long paperMapFinishTime = System.currentTimeMillis();
+        LOG.info("获取类型为" + group_code + "的试卷共耗时:" + (paperMapFinishTime - configFinishTime) + "ms");
+
+        Paper basePaper = paperMap.get(group_code);
+        if (basePaper == null) {
+            LOG.error("该考试和课程下调卷规则中该类型试卷不存在,请检查调卷规则,调卷程序退出");
+            returnMap.put("errorMsg", "该考试和课程下调卷规则中该类型试卷不存在,请重新制定调卷规则");
+            return returnMap;
+        }
+        String basePaperId = basePaper.getId();
+        LOG.info("将原始试卷:" + basePaperId + "根据规则重新组卷");
+        int upSetQuestionOrder = extractConfig.getScrambling_the_question_order();
+        int upSetOptionOrder = extractConfig.getScrambling_the_option_order();
+        // 不乱序直接调卷
+        if (upSetQuestionOrder == 0 && upSetOptionOrder == 0) {
+            PaperDto paperDto = getPaperDtoByPaper(basePaper, basePaperId);
+            long paperDtoFinishTime = System.currentTimeMillis();
+            LOG.info("获取试卷Dto共耗时:" + (paperDtoFinishTime - paperMapFinishTime) + "ms");
+            returnMap.put("paperDto", paperDto);
+            LOG.info("调卷完成");
+            LOG.info("总共耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
+        } else {
+            // 乱序重新生成试卷
+            Paper newPaper = this.recombinationPaper(basePaper, PaperType.STUDENT_EXAM, upSetQuestionOrder,
+                    upSetOptionOrder);
+            LOG.info("根据新试卷 paperId:" + newPaper.getId() + "组装PaperDto后返回");
+
+            long genPaperFinishTime = System.currentTimeMillis();
+            LOG.info("组卷共耗时:" + (genPaperFinishTime - paperMapFinishTime) + "ms");
+            PaperDto paperDto = getPaperDtoByPaper(newPaper, basePaperId);
+
+            long paperDtoFinishTime = System.currentTimeMillis();
+            LOG.info("获取试卷Dto共耗时:" + (paperDtoFinishTime - genPaperFinishTime) + "ms");
+
+            returnMap.put("paperDto", paperDto);
+            LOG.info("调卷完成");
+            LOG.info("总共耗时:" + (System.currentTimeMillis() - beginTime) + "ms");
+        }
+        return returnMap;
+    }
+
+    @Override
+    public boolean checkIsAllQbjectiveQuestion(String paperId) {
+        // 优先从redis中获取缓存dto
+        PaperDto cachePaperDto = redisClient.get(CacheConstants.CACHE_Q_TEMP_PAPER + paperId, PaperDto.class, DEFAULT_TIME_OUT);
+        if (cachePaperDto != null) {
+            return cachePaperDto.isAllQbjectiveQuestion();
+        }
+
+        Paper paper = Model.of(paperRepo.findById(paperId));
+        List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+            Question question = paperDetailUnit.getQuestion();
+            // 填空或问答
+            if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
+                    || question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
+                return false;
+            }
+            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                List<Question> subQuestions = question.getSubQuestions();
+                for (Question subQuestion : subQuestions) {
+                    if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
+                            || subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    public boolean checkIsAllQbjectiveByPdu(List<PaperDetailUnit> paperDetailUnits) {
+        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+            Question question = paperDetailUnit.getQuestion();
+            // 填空或问答
+            if (question.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
+                    || question.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
+                return false;
+            }
+            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                List<Question> subQuestions = question.getSubQuestions();
+                for (Question subQuestion : subQuestions) {
+                    if (subQuestion.getQuestionType() == QuesStructType.FILL_BLANK_QUESTION
+                            || subQuestion.getQuestionType() == QuesStructType.TEXT_ANSWER_QUESTION) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Map<String, String> makePaperByConfig(ExtractConfig extractConfig) {
+        Map<String, String> finishedPaperIdMap = new HashMap<>();
+        if (extractConfig == null) {
+            throw new RuntimeException("调卷规则不存在");
+        }
+        // 获得规则中设置的试卷
+        Map<String, Paper> paperMap = this.getExamPaperByProbability(extractConfig.getExamPaperList());
+        if (paperMap.isEmpty()) {
+            throw new RuntimeException("抽取试卷失败");
+        }
+        for (Map.Entry<String, Paper> entry : paperMap.entrySet()) {
+            String key = entry.getKey();
+            // 根据原有试卷重新组卷得到新试卷
+            /*
+             * Paper newPaper = this.recombinationPaper(entry.getValue(), PaperType.PREVIEW,
+             * extractConfig.getScrambling_the_question_order(),
+             * extractConfig.getScrambling_the_option_order());
+             */
+            finishedPaperIdMap.put(key, entry.getValue().getId());
+        }
+        return finishedPaperIdMap;
+    }
+
+    /**
+     * 重组试卷,生成新的试卷
+     *
+     * @param paper              选中的试卷
+     * @param upSetQuestionOrder 客观题小题乱序 1:乱序 0:不乱序
+     * @param upSetOptionOrder   客观题选项乱序 1:乱序 0:不乱序
+     * @return
+     */
+    public Paper recombinationPaper(Paper paper, PaperType paperType, int upSetQuestionOrder, int upSetOptionOrder) {
+
+        // 将小题全部取出来,只取一次
+        List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+        // 获取大题
+        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
+
+        // 抽取大题号对应的小题
+        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
+                .collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
+        // 最终保存的所有小题
+        List<PaperDetailUnit> savePaperDetailUnits = new ArrayList<>();
+        // 保存试卷信息
+        paper.setId(null);
+        paper.setPaperType(paperType);
+        Paper newPaper = paperRepo.insert(paper);
+
+        for (int i = 0; i < paperDetails.size(); i++) {
+            PaperDetail paperDetail = paperDetails.get(i);
+
+            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
+            if (paperDetailUnits == null || paperDetailUnits.size() == 0) {
+                continue;
+            }
+            Collections.sort(paperDetailUnits);
+
+            // 将大题中最小的number取出
+            PaperDetailUnit topDetailUnit = paperDetailUnits.get(0);
+            int minNumber = topDetailUnit.getNumber();
+
+            // 小题乱序
+            if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
+                if ((topDetailUnit.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                        || topDetailUnit.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION
+                        || topDetailUnit.getQuestionType() == QuesStructType.BOOL_ANSWER_QUESTION)
+                        && upSetQuestionOrder == 1) {
+                    Collections.shuffle(paperDetailUnits);// 打乱小题List
+                }
+            }
+            // 设置大题信息
+            paperDetail.setId(null);
+            paperDetail.setPaper(newPaper);
+
+            for (int j = 0; j < paperDetailUnits.size(); j++) {
+                // 重新设置保存PaperDetailUnit
+                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
+                paperDetailUnit.setPaperType(paperType);
+                paperDetailUnit.setPaper(newPaper);
+                paperDetailUnit.setPaperDetail(paperDetail);
+                paperDetailUnit.setNumber(minNumber + j); // 重新设置序号
+                reSavePaperDetailUtilAndQuestion(paperDetailUnit, upSetOptionOrder);
+                savePaperDetailUnits.add(paperDetailUnit);
+            }
+
+        }
+        // 保存大题信息
+        paperDetailRepo.insert(paperDetails);
+        // 保存小题信息
+        paperDetailUnitRepo.insert(savePaperDetailUnits);
+
+        // 清空所有list
+        allPaperDetailUnits.clear();
+        savePaperDetailUnits.clear();
+        paperDetails.clear();
+
+        return newPaper;
+    }
+
+    /**
+     * 每个试卷类型取出一套试卷 { A:Paper, B:Paper } A是试卷类型 Paper是A类型下选定的试卷
+     *
+     * @param examPaperList
+     * @return
+     */
+    private Map<String, Paper> getExamPaperByProbability(List<ExamPaper> examPaperList) {
+        Map<String, Paper> paperByTypeMap = new HashMap<>();
+        if (examPaperList == null || examPaperList.size() == 0) {
+            throw new RuntimeException("可供抽取的试卷集合为空,无法抽取试卷");
+        }
+
+        Map<String, List<ExamPaper>> examPaperMap = new HashMap<>();
+        for (int i = 0; i < examPaperList.size(); i++) {
+            ExamPaper examPaper = examPaperList.get(i);
+            if (!examPaperMap.containsKey(examPaper.getGroupCode())) {
+                if (examPaper.getPaper() != null) {
+                    List<ExamPaper> epList = new ArrayList<>();
+                    epList.add(examPaper);
+                    examPaperMap.put(examPaper.getGroupCode(), epList);
+                }
+            } else {
+                if (examPaper.getPaper() != null) {
+                    List<ExamPaper> epList = examPaperMap.get(examPaper.getGroupCode());
+                    epList.add(examPaper);
+                }
+            }
+        }
+
+        if (examPaperMap != null) {
+            Set<String> keys = examPaperMap.keySet();
+            Iterator<String> it = keys.iterator();
+            while (it.hasNext()) {
+                String key = it.next();
+                Paper paper = this.getPaperByProbability(examPaperMap.get(key));
+
+                if (paper == null) {
+                    continue;
+                }
+
+                // 不能用原来的paper对象,否则examPaperList中的paper对象会被覆盖
+                Paper newPaper = Model.of(paperRepo.findById(paper.getId()));
+                paperByTypeMap.put(key, newPaper);
+            }
+        }
+
+        return paperByTypeMap;
+    }
+
+    /**
+     * 根据设定几率取出一套试卷
+     *
+     * @param examPaperList
+     * @return
+     */
+    private Paper getPaperByProbability(List<ExamPaper> examPaperList) {
+        int sum = 0;
+        for (int i = 0; i < examPaperList.size(); i++) {
+            sum += examPaperList.get(i).getWeight();
+        }
+
+        // 从1开始
+        Integer rand = new Random().nextInt(sum) + 1;
+        for (int i = 0; i < examPaperList.size(); i++) {
+            rand -= examPaperList.get(i).getWeight();
+            // 选中
+            if (rand <= 0) {
+                return examPaperList.get(i).getPaper();
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 重新设置并保存paperDetailUnit和question
+     *
+     * @param paperDetailUnit
+     * @param upSetOptionOrder
+     */
+    private void reSavePaperDetailUtilAndQuestion(PaperDetailUnit paperDetailUnit, Integer upSetOptionOrder) {
+        Question question = paperDetailUnit.getQuestion();
+        // 选项乱序
+        if (upSetOptionOrder == 1) {
+            // 单选或多选
+            if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                    || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                List<String> numberList = new ArrayList<>();
+                List<QuesOption> options = question.getQuesOptions();
+                for (int k = 0; k < options.size(); k++) {
+                    QuesOption quesOption = options.get(k);
+                    numberList.add(quesOption.getNumber());
+                }
+                Collections.shuffle(numberList); // 打乱number顺序
+                paperDetailUnit.setOptionOrder(StringUtils.join(numberList.toArray(), ","));// 设置option顺序
+            }
+            // 套题,套题下小题不乱序,选择题选项乱序
+            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                List<Question> subQuestions = question.getSubQuestions();
+                StringBuffer optionOrder = new StringBuffer();
+                for (int m = 0; m < subQuestions.size(); m++) {
+                    Question subQuestion = subQuestions.get(m);
+                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                            || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                        List<String> numberList = new ArrayList<>();
+                        List<QuesOption> options = subQuestion.getQuesOptions();
+                        for (int n = 0; n < options.size(); n++) {
+                            QuesOption quesOption = options.get(n);
+                            numberList.add(quesOption.getNumber());
+                        }
+                        Collections.shuffle(numberList); // 打乱number顺序
+                        optionOrder.append(StringUtils.join(numberList.toArray(), ",")).append(";");
+                    }
+                }
+                paperDetailUnit.setOptionOrder(optionOrder.toString()); // 设置option顺序
+            }
+        }
+        paperDetailUnit.setId(null);
+    }
+
+    @Override
+    public List<String> getExamPaperId(String courseCode, String orgId) {
+        Assert.hasLength(courseCode, "courseCode不能为空");
+        Assert.hasLength(orgId, "orgId不能为空");
+        List<String> paperIdList = new ArrayList<>();
+        Query query = new Query();
+        query.addCriteria(Criteria.where("courseCode").is(courseCode));
+        query.addCriteria(Criteria.where("orgId").is(orgId));
+        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
+        for (ExtractConfig extractConfig : extractConfigList) {
+            List<ExamPaper> examPaperList = extractConfig.getExamPaperList();
+            if (examPaperList != null && examPaperList.size() > 0) {
+                for (ExamPaper examPaper : examPaperList) {
+                    paperIdList.add(examPaper.getPaper().getId());
+                }
+            }
+        }
+        return paperIdList;
+    }
+
+    /**
+     * 根据paper得到PaperDto
+     *
+     * @param paper
+     * @return
+     */
+    private PaperDto getPaperDtoByPaper(Paper paper, String basePaperId) {
+        long beginTime = System.currentTimeMillis();
+        // 没有则重新组装
+        PaperDto paperDto = paperDtoAssembler.toDto(paper);
+        paperDto.setBasePaperId(basePaperId);
+        paperDto.setAllQbjectiveQuestion(checkIsAllQbjectiveQuestion(basePaperId));
+
+        long paperDtoEndTime = System.currentTimeMillis();
+        LOG.info("单独组装paperDto耗时:" + (paperDtoEndTime - beginTime) + "ms");
+
+        // 将小题全部取出来,只取一次
+        List<PaperDetailUnit> allPaperDetailUnits = paperDetailUnitRepo.findByPaper(paper);
+        long pduEndTime = System.currentTimeMillis();
+        LOG.info("数据库取小题耗时:" + (pduEndTime - paperDtoEndTime) + "ms");
+        Collections.sort(allPaperDetailUnits);
+        long pduSortEndTime = System.currentTimeMillis();
+        LOG.info("排序小题耗时:" + (pduSortEndTime - pduEndTime) + "ms");
+
+        // 获取大题
+        List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(paper);
+        long pdEndTime = System.currentTimeMillis();
+        LOG.info("数据库取大题耗时:" + (pdEndTime - pduSortEndTime) + "ms");
+        Collections.sort(paperDetails);
+        long pdSortEndTime = System.currentTimeMillis();
+        LOG.info("排序大题耗时:" + (pdSortEndTime - pdEndTime) + "ms");
+
+        // 抽取大题Id对应的小题
+        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
+                .collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
+        long pduMapEndTime = System.currentTimeMillis();
+        LOG.info("获取大题与小题对应关系耗时:" + (pduMapEndTime - pdSortEndTime) + "ms");
+
+        // 获取大题Dto
+        List<PaperDetailDto> paperDetailDtos = paperDetailDtoAssembler.toDtoList(paperDetails);
+        paperDto.setPaperDetails(paperDetailDtos);
+        long paperDetailDtoEndTime = System.currentTimeMillis();
+        LOG.info("单独组装paperDetailDto耗时:" + (paperDetailDtoEndTime - pduMapEndTime) + "ms");
+
+        // 封装小题
+        for (int i = 0; i < paperDetailDtos.size(); i++) {
+            // 根据大题查出大题下面的小题
+            PaperDetail paperDetail = paperDetails.get(i);
+
+            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
+
+            List<PaperDetailUnitDto> paperDetailUnitDtos = new ArrayList<>();
+            for (int j = 0; j < paperDetailUnits.size(); j++) {
+                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
+                if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
+                    break;
+                }
+                // 设置答案
+                setSelectQuestionAnswerUnit(paperDetailUnit);
+
+                PaperDetailUnitDto unitDto = paperDetailUnitDtoAssembler.toDto(paperDetailUnit);
+                /**
+                 * 此处不能传questionId,需要传paperDetailUnitId 因为选项乱序在paperDetailUnit里
+                 * unitDto.setQuesId(paperDetailUnit.getQuestion().getId());
+                 */
+                unitDto.setQuesId(paperDetailUnit.getId());
+                String answer = paperDetailUnit.getQuestion().getQuesAnswer();
+                if (StringUtils.isNotEmpty(answer)) {
+                    unitDto.setAnswer(answer);
+                }
+                if (unitDto.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {// 假如是套题
+                    List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
+                    List<SubQuestionDto> subQuesDtos = subQuestionDtoAssembler.toDtoList(subQuesList);
+                    for (int m = 0; m < subQuesList.size(); m++) {
+                        List<QuesOptionDto> quesOptionDtos = subQuestionDtoAssembler
+                                .toOptionDtoList(subQuesList.get(m).getQuesOptions());
+                        subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
+                        if (StringUtils.isNotEmpty(subQuesList.get(m).getQuesAnswer())) {
+                            subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
+                        }
+                        subQuesDtos.get(m).setNumber(m + 1);
+                        // 套题分数从小题类中取值
+                        subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
+                    }
+                    unitDto.setSubQuestions(subQuesDtos);
+                }
+                paperDetailUnitDtos.add(unitDto);
+            }
+            paperDetailDtos.get(i).setPaperDetailUnits(paperDetailUnitDtos);
+            paperDetailDtos.get(i).setCnNum(CommonUtils.toCHNum(paperDetailDtos.get(i).getNumber()));
+        }
+        long paperDetailUnitDtoEndTime = System.currentTimeMillis();
+        LOG.info("单独组装paperDetailUnitDto耗时:" + (paperDetailUnitDtoEndTime - paperDetailDtoEndTime) + "ms");
+
+        // 将重新组装的dto放进缓存
+        redisClient.set(CacheConstants.CACHE_Q_TEMP_PAPER + paperDto.getId(), paperDto, DEFAULT_TIME_OUT);
+        return paperDto;
+    }
+
+    /**
+     * 测试获取paperDto
+     *
+     * @param basePaperId
+     * @return
+     */
+    @Deprecated
+    public PaperDto getPaperDtoByPaperNew(String basePaperId) {
+
+        long beginTime = System.currentTimeMillis();
+        // 没有则重新组装
+        Paper paper = Model.of(paperRepo.findById(basePaperId));
+        PaperDto paperDto = paperDtoAssembler.toDto(paper);
+        paperDto.setBasePaperId(basePaperId);
+
+        long paperDtoEndTime = System.currentTimeMillis();
+        LOG.info("单独组装paperDto耗时:" + (paperDtoEndTime - beginTime) + "ms");
+
+        // 将小题全部取出来,只取一次
+        // List<PaperDetailUnit> allPaperDetailUnits =
+        // detailUnitNativeRepo.findByPaperId(paper.getId());
+        List<PaperDetailUnit> allPaperDetailUnits = new ArrayList<>();
+
+        long pduEndTime = System.currentTimeMillis();
+        LOG.info("数据库取小题耗时:" + (pduEndTime - paperDtoEndTime) + "ms");
+        Collections.sort(allPaperDetailUnits);
+        long pduSortEndTime = System.currentTimeMillis();
+        LOG.info("排序小题耗时:" + (pduSortEndTime - pduEndTime) + "ms");
+
+        paperDto.setAllQbjectiveQuestion(checkIsAllQbjectiveByPdu(allPaperDetailUnits));
+        long isAllObjEndtime = System.currentTimeMillis();
+        LOG.info("设置客观题耗时:" + (isAllObjEndtime - pduSortEndTime) + "ms");
+
+        // 获取大题
+        List<PaperDetail> paperDetails = paperDetailRepo.findByPaper(paper);
+        long pdEndTime = System.currentTimeMillis();
+        LOG.info("数据库取大题耗时:" + (pdEndTime - isAllObjEndtime) + "ms");
+        Collections.sort(paperDetails);
+        long pdSortEndTime = System.currentTimeMillis();
+        LOG.info("排序大题耗时:" + (pdSortEndTime - pdEndTime) + "ms");
+
+        // 抽取大题Id对应的小题
+        Map<String, List<PaperDetailUnit>> pduMap = allPaperDetailUnits.stream()
+                .collect(Collectors.groupingBy(PaperDetailUnit::getDetailId));
+        long pduMapEndTime = System.currentTimeMillis();
+        LOG.info("获取大题与小题对应关系耗时:" + (pduMapEndTime - pdSortEndTime) + "ms");
+
+        // 获取大题Dto
+        List<PaperDetailDto> paperDetailDtos = paperDetailDtoAssembler.toDtoList(paperDetails);
+        paperDto.setPaperDetails(paperDetailDtos);
+        long paperDetailDtoEndTime = System.currentTimeMillis();
+        LOG.info("单独组装paperDetailDto耗时:" + (paperDetailDtoEndTime - pduMapEndTime) + "ms");
+
+        // 封装小题
+        for (int i = 0; i < paperDetailDtos.size(); i++) {
+            // 根据大题查出大题下面的小题
+            PaperDetail paperDetail = paperDetails.get(i);
+
+            List<PaperDetailUnit> paperDetailUnits = pduMap.get(paperDetail.getId());
+
+            List<PaperDetailUnitDto> paperDetailUnitDtos = new ArrayList<>();
+            for (int j = 0; j < paperDetailUnits.size(); j++) {
+                PaperDetailUnit paperDetailUnit = paperDetailUnits.get(j);
+                if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
+                    break;
+                }
+                // 设置答案
+                setSelectQuestionAnswerUnit(paperDetailUnit);
+
+                PaperDetailUnitDto unitDto = paperDetailUnitDtoAssembler.toDto(paperDetailUnit);
+                /**
+                 * 此处不能传questionId,需要传paperDetailUnitId 因为选项乱序在paperDetailUnit里
+                 * unitDto.setQuesId(paperDetailUnit.getQuestion().getId());
+                 */
+                unitDto.setQuesId(paperDetailUnit.getId());
+                String answer = paperDetailUnit.getQuestion().getQuesAnswer();
+                if (StringUtils.isNotEmpty(answer)) {
+                    unitDto.setAnswer(answer);
+                }
+                if (unitDto.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {// 假如是套题
+                    List<Question> subQuesList = paperDetailUnit.getQuestion().getSubQuestions();
+                    List<SubQuestionDto> subQuesDtos = subQuestionDtoAssembler.toDtoList(subQuesList);
+                    for (int m = 0; m < subQuesList.size(); m++) {
+                        List<QuesOptionDto> quesOptionDtos = subQuestionDtoAssembler
+                                .toOptionDtoList(subQuesList.get(m).getQuesOptions());
+                        subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
+                        if (StringUtils.isNotEmpty(subQuesList.get(m).getQuesAnswer())) {
+                            subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
+                        }
+                        subQuesDtos.get(m).setNumber(m + 1);
+                        // 套题分数从小题类中取值
+                        subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
+                    }
+                    unitDto.setSubQuestions(subQuesDtos);
+                }
+                paperDetailUnitDtos.add(unitDto);
+            }
+            paperDetailDtos.get(i).setPaperDetailUnits(paperDetailUnitDtos);
+            paperDetailDtos.get(i).setCnNum(CommonUtils.toCHNum(paperDetailDtos.get(i).getNumber()));
+        }
+        long paperDetailUnitDtoEndTime = System.currentTimeMillis();
+        LOG.info("单独组装paperDetailUnitDto耗时:" + (paperDetailUnitDtoEndTime - paperDetailDtoEndTime) + "ms");
+        return paperDto;
+    }
+
+    private void setSelectQuestoionAnswer(List<PaperDetailUnit> paperDetailUnits) {
+        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+            if (paperDetailUnit == null || paperDetailUnit.getQuestion() == null) {
+                break;
+            }
+            String optionOrder = paperDetailUnit.getOptionOrder();
+            Question question = paperDetailUnit.getQuestion();
+            quesService.setSelectQuestionAnswer(question, optionOrder);
+        }
+    }
+
+    private void setSelectQuestionAnswerUnit(PaperDetailUnit paperDetailUnit) {
+        Question question = paperDetailUnit.getQuestion();
+        if (paperDetailUnit == null || question == null) {
+            return;
+        }
+        String optionOrder = paperDetailUnit.getOptionOrder();
+        quesService.setSelectQuestionAnswer(question, optionOrder);
+    }
+
+    /**
+     * 根据paperDetailUnitId抽取单个试题 根据paperDetailUnitId中设置的option顺序对option排序
+     */
+    @Override
+    public QuestionDto extractExamQuestion(String examId, String courseCode, String groupCode,
+                                           String paperDetailUnitId) {
+        PaperDetailUnit paperDetailUnit = Model.of(paperDetailUnitRepo.findById(paperDetailUnitId));
+        List<PaperDetailUnit> paperDetailUnits = new ArrayList<>();
+        paperDetailUnits.add(paperDetailUnit);
+        // 设置答案
+        setSelectQuestoionAnswer(paperDetailUnits);
+        // 重新对选择题option进行排序(多选、单选、套题下选择题)
+        reorderChoicequestionOption(paperDetailUnit);
+        Question ques = paperDetailUnit.getQuestion();
+        quesService.formatQues(ques);
+        QuestionDto dto = BeanCopierUtil.copyProperties(ques, QuestionDto.class);
+        dto.setScore(paperDetailUnit.getScore());
+        dto.setQuesOptions(buildQuestionOptionDto(ques.getQuesOptions()));
+        if (ques.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+            List<Question> subQuesList = ques.getSubQuestions();
+            List<SubQuestionDto> subQuesDtos = new ArrayList<>();
+            for (Question question : subQuesList) {
+                SubQuestionDto subQuestionDto = new SubQuestionDto();
+                BeanUtils.copyProperties(question, subQuestionDto);
+                subQuesDtos.add(subQuestionDto);
+            }
+            for (int m = 0; m < subQuesList.size(); m++) {
+                List<QuesOptionDto> quesOptionDtos = BeanCopierUtil
+                        .copyPropertiesOfList(subQuesList.get(m).getQuesOptions(), QuesOptionDto.class);
+                subQuesDtos.get(m).setQuesOptions(quesOptionDtos);
+                subQuesDtos.get(m).setQuesAnswer(subQuesList.get(m).getQuesAnswer());
+                subQuesDtos.get(m).setNumber(m + 1);
+                // 套题分数从小题类中取值
+                subQuesDtos.get(m).setScore(paperDetailUnit.getSubScoreList().get(m));
+                dto.setSubQuestions(subQuesDtos);
+            }
+        }
+        if (ques.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                || ques.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+            dto.setQuesAnswer(ques.getQuesAnswer());
+        }
+        appendAudioFlag(dto, examId, courseCode, groupCode);
+        return dto;
+    }
+
+    private List<QuesOptionDto> buildQuestionOptionDto(List<QuesOption> quesOptions) {
+        if (quesOptions == null) {
+            return null;
+        }
+        List<QuesOptionDto> optionDtos = new ArrayList<>();
+        for (QuesOption option : quesOptions) {
+            QuesOptionDto optionDto = new QuesOptionDto();
+            optionDto.setNumber(option.getNumber());
+            optionDto.setOptionBody(option.getOptionBody());
+            optionDtos.add(optionDto);
+        }
+        return optionDtos;
+    }
+
+    /**
+     * 1.给QuestionDto添加音频播放次数 2.给试题音频中有a标签添加url
+     *
+     * @param questionDto
+     */
+    private void appendAudioFlag(QuestionDto questionDto, String examId, String courseCode, String groupCode) {
+        if (questionDto.getHasAudio() != null && questionDto.getHasAudio() == true) {
+            // 1.判断questionDto是否含有音频,如果有添加音频播放次数
+            AudioTimeConfig audioTimeConfig = Model.of(audioTimeConfigRepo
+                    .findOne(Example.of(new AudioTimeConfig(examId, courseCode, groupCode, questionDto.getId()))));
+            questionDto.setPlayTime(audioTimeConfig != null ? audioTimeConfig.getPlayTime() : null);
+            // 2.1 取到题干,给a标签添加url
+            String quesBody = questionDto.getQuesBody();
+            questionDto.setQuesBody(buildBody(quesBody, questionDto));
+            // 2.2取到选项,给a标签添加url
+            List<QuesOptionDto> quesOptionDtoList = questionDto.getQuesOptions();
+            if (quesOptionDtoList != null && quesOptionDtoList.size() > 0) {
+                for (QuesOptionDto quesOptionDto : quesOptionDtoList) {
+                    quesOptionDto.setOptionBody(buildBody(quesOptionDto.getOptionBody(), questionDto));
+                }
+            }
+        } else {
+            questionDto.setPlayTime(null);
+        }
+    }
+
+    // 给题目和选项添加url
+    public String buildBody(String body, QuestionDto questionDto) {
+        String[] bodyStrings = body.split("></a>");
+        if (bodyStrings.length > 1) {
+            String resultBody = "";
+            for (int i = 0; i < bodyStrings.length; i++) {
+                String containAStr = bodyStrings[i];
+                if (containAStr.indexOf("<a") > -1) {
+                    String questionAudioId = matchAudioName(containAStr, "a", "id");
+                    QuestionAudio questionAudio = questionAudioService.findAudioById(questionAudioId);
+                    if (questionAudio != null) {
+                        //                        String url = sysProperty.getDomain() + questionAudio.getFileUrl();
+                        // 通用存储
+                        String url = FileStorageUtil.realPath(questionAudio.getFileUrl());
+                        if (questionDto.getPlayTime() != null) {
+                            containAStr += " question-audio url=\"" + url + "\" playTime=\"" + questionDto.getPlayTime()
+                                    + "\"" + "></a>";
+                        } else {
+                            containAStr += " question-audio url=\"" + url + "\"" + "></a>";
+                        }
+                    }
+                }
+                resultBody += containAStr;
+            }
+            return resultBody;
+        } else {
+            return body;
+        }
+    }
+
+    private String matchAudioName(String source, String element, String attr) {
+        String reg = "<" + element + "[^<>]*?\\s" + attr + "=['\"]?(.*?)['\"]?(\\s.*?)";
+        Matcher m = Pattern.compile(reg).matcher(source);
+        if (m.find()) {
+            return m.group(1);
+        }
+        return "";
+    }
+
+    /**
+     * 重新对选择题option进行排序(多选、单选、套题下选择题)
+     */
+    private void reorderChoicequestionOption(PaperDetailUnit paperDetailUnit) {
+        String optionOrder = paperDetailUnit.getOptionOrder();
+        if (StringUtil.isNotBlank(optionOrder)) {
+            Question question = paperDetailUnit.getQuestion();
+            if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                    || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                question.setQuesOptions(reorderOptionCore(question.getQuesOptions(), optionOrder));
+            }
+            if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+                List<Question> subQuestions = question.getSubQuestions();
+                int index = 0;
+                for (int k = 0; k < subQuestions.size(); k++) {
+                    Question subQuestion = subQuestions.get(k);
+                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                            || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                        subQuestion.setQuesOptions(
+                                reorderOptionCore(subQuestion.getQuesOptions(), optionOrder.split(";")[index]));
+                        index++;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 对option排序
+     *
+     * @param quesOptions
+     * @param optionOrder
+     * @return
+     */
+    private List<QuesOption> reorderOptionCore(List<QuesOption> quesOptions, String optionOrder) {
+        List<QuesOption> newQuesOptions = new ArrayList<>();
+        if (StringUtil.isBlank(optionOrder) || quesOptions.isEmpty()) {
+            return null;
+        }
+        String[] optionOrderArr = optionOrder.split(",");
+        for (int j = 0; j < optionOrderArr.length; j++) {
+            for (int k = 0; k < quesOptions.size(); k++) {
+                if (optionOrderArr[j].equals(quesOptions.get(k).getNumber())) {
+                    newQuesOptions.add(quesOptions.get(k));
+                }
+            }
+        }
+        quesOptions = null;
+        return newQuesOptions;
+    }
+
+    @Override
+    public Page<ExtractConfig> findPageExtractConfig(int currentPage, int pageSize, Long examId, String courseCode,
+                                                     String orgId, UserDataRule ud) {
+        if (ud.assertEmptyQueryResult()) {
+            return Page.empty();
+        }
+        if (examId == null) {
+            throw new StatusException("Q-", "examId is null");
+        }
+        Query query = new Query();
+        query.addCriteria(Criteria.where("orgId").is(orgId));
+        if (ud.assertNeedQueryRefIds()) {
+            query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
+        }
+        query.addCriteria(Criteria.where("examId").is(examId));
+        query.addCriteria(Criteria.where("course.enable").is("true"));
+        if (!StringUtils.isBlank(courseCode)) {
+            query.addCriteria(Criteria.where("course.code").is(courseCode));
+        }
+
+        long count = this.mongoTemplate.count(query, ExtractConfig.class);
+        query.limit(pageSize);
+        query.skip((currentPage - 1L) * pageSize);
+
+        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
+        if (extractConfigList != null && extractConfigList.size() > 0) {
+            // 调用考务rmi,获取考试信息
+            GetExamReq req = new GetExamReq();
+            req.setId(examId);
+            req.setRootOrgId(Long.valueOf(orgId));
+            GetExamResp resp = examCloudService.getExam(req);
+            ExamBean bean = resp.getExamBean();
+            for (ExtractConfig extractConfig : extractConfigList) {
+                extractConfig.setExamType(bean.getExamType());
+            }
+        }
+
+        return new PageImpl<>(extractConfigList, PageRequest.of(currentPage - 1, pageSize), count);
+    }
+
+    @Override
+    public Map<String, Object> extractPaper(String paperId) {
+        Map<String, Object> returnMap = new HashMap<>();
+        Paper paper = Model.of(paperRepo.findById(paperId));
+        if (paper == null) {
+            returnMap.put("errorMsg", "该试卷不存在");
+            return returnMap;
+        }
+        PaperDto paperDto = getPaperDtoByPaper(paper, paperId);
+        returnMap.put("paperDto", paperDto);
+        return returnMap;
+    }
+
+    @Override
+    public String getAnswerHtml(String paperId) {
+        // 1.根据id查询试卷
+        Paper paper = Model.of(paperRepo.findById(paperId));
+        // 2.定义html字符串
+        StringBuffer answerHtml = new StringBuffer("<p style=\"text-align:center;font-size:20px;font-weight:bold\">"
+                + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")" + "</p>");
+        // 添加考试说明
+        if (paper.getExamRemark() == null) {
+            answerHtml.append("<p>考试说明:</p>");
+        } else {
+            answerHtml.append("<p>考试说明:</p>" + paper.getExamRemark());
+        }
+        // 3.通过试卷获取考试端试卷结构
+        PaperDto paperDto = getPaperDtoByPaper(paper, paper.getId());
+        // 4.获取试卷所有大题,并且循环
+        List<PaperDetailDto> paperDetailDtos = paperDto.getPaperDetails();
+        for (PaperDetailDto paperDetailDto : paperDetailDtos) {
+            String title = "<p>" + paperDetailDto.getCnNum() + "、" + paperDetailDto.getName() + "(共"
+                    + paperDetailDto.getScore() + "分)" + "</p>";
+            answerHtml.append(title);
+            // 5.获取大题下面的小题,并循环
+            List<PaperDetailUnitDto> paperDetailUnitDtos = paperDetailDto.getPaperDetailUnits();
+            for (PaperDetailUnitDto paperDetailUnitDto : paperDetailUnitDtos) {
+                // 定义题干
+                String body = paperDetailUnitDto.getQuesBody().replaceAll("<span>", "").replaceAll("</span>", "")
+                        .replaceAll("###", "___");
+                ;
+                // 定义选项
+                String option = "";
+                // 定义答案
+                String answer = "";
+                // 6.如果为单选和多选
+                if (paperDetailUnitDto.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                        || paperDetailUnitDto.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                    // 获取选项
+                    List<QuesOptionDto> quesOptionDtos = paperDetailUnitDto.getQuesOptions();
+                    // 添加题干
+                    body = startWithP(body, paperDetailUnitDto.getNumber(), false);
+                    answerHtml.append(body);
+                    // 添加选项
+                    for (QuesOptionDto quesOptionDto : quesOptionDtos) {
+                        option = startWithP(quesOptionDto.getOptionBody(), Integer.parseInt(quesOptionDto.getNumber()),
+                                true);
+                        answerHtml.append(option);
+                    }
+                    answer = startWithP(paperDetailUnitDto.getAnswer(), -1, false);
+                    answerHtml.append(answer);
+                }
+                // 7.如果为套题
+                else if (paperDetailUnitDto.getSubQuestions() != null
+                        && paperDetailUnitDto.getSubQuestions().size() > 0) {
+                    // 添加主题干
+                    String bigBody = CommonUtils.relaceQuestionIdx(paperDetailUnitDto.getQuesBody(), 0);
+                    bigBody = bigBody.replaceAll("<span>", "").replaceAll("</span>", "").replaceAll("###", "___");
+                    bigBody = startWithP(bigBody, -2, false);
+                    answerHtml.append(bigBody);
+                    List<SubQuestionDto> subQuestionDtos = paperDetailUnitDto.getSubQuestions();
+                    for (SubQuestionDto subQuestionDto : subQuestionDtos) {
+                        body = subQuestionDto.getQuesBody().replaceAll("<span>", "").replaceAll("</span>", "")
+                                .replaceAll("###", "___");
+                        // 如果子题为单选或者多选
+                        if (subQuestionDto.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+                                || subQuestionDto.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+                            // 获取选项
+                            List<QuesOptionDto> quesOptionDtos = subQuestionDto.getQuesOptions();
+                            // 添加题干
+                            body = startWithP(body, subQuestionDto.getNumber(), false);
+                            answerHtml.append(body);
+                            // 添加选项
+                            for (QuesOptionDto quesOptionDto : quesOptionDtos) {
+                                option = startWithP(quesOptionDto.getOptionBody(),
+                                        Integer.parseInt(quesOptionDto.getNumber()), true);
+                                answerHtml.append(option);
+                            }
+                            answer = startWithP(subQuestionDto.getQuesAnswer(), -1, false);
+                            answerHtml.append(answer);
+                        } else {
+                            // 添加题干
+                            body = startWithP(body, subQuestionDto.getNumber(), false);
+                            answerHtml.append(body);
+                            // 添加答案
+                            answer = startWithP(subQuestionDto.getQuesAnswer(), -1, false);
+                            answerHtml.append(answer);
+                        }
+                    }
+                } else {
+                    // 添加题干
+                    body = startWithP(body, paperDetailUnitDto.getNumber(), false);
+                    answerHtml.append(body);
+                    // 添加答案
+                    answer = startWithP(paperDetailUnitDto.getAnswer(), -1, false);
+                    answerHtml.append(answer);
+                }
+            }
+        }
+        return answerHtml.toString();
+    }
+
+    public String startWithP(String s, Integer number, boolean option) {
+        if (s == null) {
+            s = "<p></p>";
+        }
+        if (number == -2) {
+            if (s.startsWith("<p>")) {
+                s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceAll("<p style=\"margin-left:40px;\">",
+                        "<p style=\"margin-left:20px;\">");
+            } else {
+                s = "<p style=\"margin-left:20px;color:red;\">" + s + "</p>";
+            }
+        } else if (number == -1) {
+            if (s.startsWith("<p>")) {
+                s = s.replaceAll("<p>", "<p style=\"margin-left:65px;\">")
+                        .replaceFirst("<p style=\"margin-left:65px;\">",
+                                "<p style=\"margin-left:20px;color:red;\">" + "答案:")
+                        .replace("<p style=\"margin-left:65px;\">", "<p style=\"margin-left:65px;color:red;\">");
+            } else {
+                s = "<p style=\"margin-left:20px;color:red;\">" + "答案:" + s + "</p>";
+            }
+        } else {
+            if (option) {
+                if (s.startsWith("<p>")) {
+                    s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceFirst(
+                            "<p style=\"margin-left:40px;\">",
+                            "<p style=\"margin-left:20px;\">" + CommonUtils.getOptionNum(number - 1) + ".");
+                } else {
+                    s = "<p style=\"margin-left:20px;\">" + CommonUtils.getOptionNum(number - 1) + "." + s + "</p>";
+                }
+            } else {
+                if (s.startsWith("<p>")) {
+                    s = s.replaceAll("<p>", "<p style=\"margin-left:40px;\">").replaceFirst(
+                            "<p style=\"margin-left:40px;\">", "<p style=\"margin-left:20px;\">" + number + ".");
+                } else {
+                    s = "<p style=\"margin-left:20px;\">" + number + "." + s + "</p>";
+                }
+            }
+        }
+        return s;
+    }
+
+    @Override
+    public List<CouresInfo> findCourseByExtractConfig(Long examId, String orgId, UserDataRule ud) {
+        if (ud.assertEmptyQueryResult()) {
+            return new ArrayList<>();
+        }
+        if (examId == null) {
+            throw new StatusException("500", "examId is null");
+        }
+        // 从考务查询改考试下的所有开启课程
+        List<CouresInfo> courseInfoList = new ArrayList<>();
+        GetExamCourseListReq req = new GetExamCourseListReq();
+        req.setExamId(examId);
+        req.setCourseEnable(true);
+        Long start = 1l;
+        int count = 0;
+        while (true) {
+            req.setStart(start);
+            GetExamCourseListResp resp = examCloudService.getExamCourseList(req);
+            for (ExamCourseRelationBean bean : resp.getRelationList()) {
+                CouresInfo info = new CouresInfo(bean);
+                courseInfoList.add(info);
+            }
+            if (start.equals(resp.getNext())) {
+                break;
+            } else {
+                start = resp.getNext();
+            }
+            count++;
+            if (count > 1000) {
+                throw new StatusException("Q-", "考试下课程的数据量过大");
+            }
+        }
+        if (CollectionUtils.isEmpty(courseInfoList)) {
+            return null;
+        }
+        // 查询已经改考试下已经制定的课程的调卷规则
+        Query query = new Query();
+        query.addCriteria(Criteria.where("orgId").is(orgId));
+        query.addCriteria(Criteria.where("examId").is(examId));
+        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
+        if (extractConfigList != null && extractConfigList.size() > 0) {
+            for (ExtractConfig extractConfig : extractConfigList) {
+                CouresInfo info = new CouresInfo();
+                info.setCourseCode(extractConfig.getCourseCode());
+                if (courseInfoList.contains(info)) {
+                    courseInfoList.remove(info);
+                }
+            }
+        }
+        if (ud.assertNeedQueryRefIds() && courseInfoList.size() > 0) {
+            List<CouresInfo> ret = new ArrayList<>();
+            for (CouresInfo info : courseInfoList) {
+                if (ud.getRefIds().contains(info.getCourseId())) {
+                    ret.add(info);
+                }
+            }
+            return ret;
+        }
+        return courseInfoList;
+    }
+
+    @Override
+    public List<ExtractConfig> findExtractConfig(Long examId) {
+        if (examId == null) {
+            throw new StatusException("1001", "examId is null");
+        }
+        Query query = new Query();
+        query.addCriteria(Criteria.where("examId").is(examId));
+        query.addCriteria(Criteria.where("course.enable").is("true"));
+
+        List<ExtractConfig> extractConfigList = this.mongoTemplate.find(query, ExtractConfig.class);
+
+        return extractConfigList;
+    }
 
 }

+ 2 - 2
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/GenPaperService.java

@@ -13,6 +13,7 @@ import java.util.stream.Collectors;
 
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -20,7 +21,6 @@ import org.springframework.stereotype.Service;
 
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
 import cn.com.qmth.examcloud.core.questions.base.Model;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperStructType;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
@@ -915,7 +915,7 @@ public class GenPaperService {
                 BigDecimal b2 = BigDecimal.valueOf(pd.getScore() == null ? 0d : pd.getScore());
                 b1 = b1.add(b2);
             }
-            paper.setId(IdUtils.uuid());
+            paper.setId(new ObjectId().toString());
             paper.setName(genPaperDto.getPaperName());
             paper.setCourseNo(genPaperDto.getCourseNo());
             paper.setCourseName(genPaperDto.getCourseName());

+ 29 - 47
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/PaperDetailUnitServiceImpl.java

@@ -4,6 +4,7 @@ import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.bson.types.ObjectId;
 import org.jsoup.Jsoup;
@@ -33,6 +34,8 @@ 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.PaperService;
 import cn.com.qmth.examcloud.core.questions.service.QuesService;
+import cn.com.qmth.examcloud.core.questions.service.RandomPaperQuestionService;
+import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperDetailUnitDto;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperDetailUnitExp;
 import cn.com.qmth.examcloud.core.questions.service.cache.BasePaperCache;
 import cn.com.qmth.examcloud.core.questions.service.cache.ExtractConfigPaperCache;
@@ -75,7 +78,9 @@ public class PaperDetailUnitServiceImpl implements PaperDetailUnitService {
 
     @Autowired
     RedisTemplate<String, Object> redisTemplate;
-
+    @Autowired
+    private RandomPaperQuestionService randomPaperQuestionService;
+    
     /**
      * 根据Id获得对应的试题对象
      *
@@ -130,15 +135,34 @@ public class PaperDetailUnitServiceImpl implements PaperDetailUnitService {
     	}
 		return o;
 	}
+    
+    @Override
+    public boolean paperInUse(String questionId) {
+    	 Query query = new Query();
+         query.addCriteria(Criteria.where("question.$id").is(new ObjectId(questionId)));
+         query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
+         List<PaperDetailUnitDto> units=this.mongoTemplate.find(query, PaperDetailUnitDto.class,"paperDetailUnit");
+         if(CollectionUtils.isEmpty(units)) {
+        	 return false;
+         }
+         for(PaperDetailUnitDto dto:units) {
+        	 if(dto.getPaper().getInUse()!=null&&dto.getPaper().getInUse()==1) {
+        		 return true;
+        	 }
+         }
+         return false;
+    }
     /**
      * 保存小题
      */
     public PaperDetailUnit savePaperDetailUnit(PaperDetailUnitExp updateUnit, User user) {
     	StringBuilder sb=new StringBuilder();
         PaperDetailUnit baseUnit = Model.of(paperDetailUnitRepo.findById(updateUnit.getId()));
-        Paper cpaper=baseUnit.getPaper();
-        if(cpaper.getInUse()!=null&&cpaper.getInUse()==1) {
-        	checkUpdate(updateUnit, baseUnit);
+        if(paperInUse(baseUnit.getQuestion().getId())) {
+        	PaperUtil.checkUpdate(updateUnit, baseUnit,"试卷已调用,");
+        }
+        if(randomPaperQuestionService.existQuestion(baseUnit.getQuestion().getId())) {
+        	PaperUtil.checkUpdateOption(updateUnit, baseUnit,"小题已被抽题模板使用,");
         }
         Question baseQuestion = baseUnit.getQuestion();
         Question updateQuestion = updateUnit.getQuestion();
@@ -266,49 +290,7 @@ public class PaperDetailUnitServiceImpl implements PaperDetailUnitService {
         return baseUnit;
     }
     
-    private void checkUpdate(PaperDetailUnitExp updateUnit,PaperDetailUnit baseUnit) {
-    	Question baseQuestion = baseUnit.getQuestion();
-        Question updateQuestion = updateUnit.getQuestion();
-    	if (baseUnit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-            if (updateQuestion.getId().equals(baseQuestion.getId())) {
-            	return;
-            } else {
-                int size = baseQuestion.getSubQuestions().size();
-                // 判断更新的对象是哪个子题
-                for (int index = 1; index <= size; index++) {
-
-                    Question sub = baseQuestion.getSubQuestions().get(index - 1);
-                    if (updateQuestion.getId().equals(sub.getId())) {
-                    	if(updateQuestion.getScore().doubleValue()!=baseUnit.getSubScoreList().get(index - 1).doubleValue()) {
-                    		throw new StatusException("500", "试卷已调用,不能修改分数");
-                    	}
-                        checkUpdate(updateQuestion, sub);
-
-                    }
-                }
-            }
-        } else {
-        	if(updateUnit.getScore().doubleValue()!=baseUnit.getScore().doubleValue()) {
-        		throw new StatusException("500", "试卷已调用,不能修改分数");
-        	}
-        	checkUpdate(updateQuestion, baseQuestion);
-        }
-    }
-    private void checkUpdate(Question update,Question base) {
-    	if(QuesStructType.SINGLE_ANSWER_QUESTION.equals(base.getQuestionType())
-    			||QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(base.getQuestionType())){
-	    	if(update.getQuesOptions().size()!=base.getQuesOptions().size()) {
-	    		throw new StatusException("500", "试卷已调用,不能修改选项数量");
-	    	}
-    	}
-    	if(QuesStructType.SINGLE_ANSWER_QUESTION.equals(base.getQuestionType())
-    			||QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(base.getQuestionType())
-    			||QuesStructType.BOOL_ANSWER_QUESTION.equals(base.getQuestionType())){
-	    	if(!update.getQuesAnswer().equals(base.getQuesAnswer())) {
-	    		throw new StatusException("500", "试卷已调用,不能修改客观题答案");
-	    	}
-    	}
-    }
+    
     
     private String getQuestionChangeInfo(Question old,Question now) {
     	StringBuilder sb=new StringBuilder();

+ 2396 - 2213
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/PaperServiceImpl.java

@@ -22,6 +22,8 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
+import javax.annotation.Resource;
+
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.ArrayUtils;
@@ -30,6 +32,7 @@ import org.apache.poi.xssf.usermodel.XSSFCell;
 import org.apache.poi.xssf.usermodel.XSSFRow;
 import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.bson.types.ObjectId;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.nlpcn.commons.lang.util.StringUtil;
@@ -96,6 +99,8 @@ import cn.com.qmth.examcloud.core.questions.service.PaperDetailService;
 import cn.com.qmth.examcloud.core.questions.service.PaperDetailUnitService;
 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.RandomPaperQuestionService;
+import cn.com.qmth.examcloud.core.questions.service.RandomPaperService;
 import cn.com.qmth.examcloud.core.questions.service.bean.PaperId;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.DownloadPaperDto;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.ObjectiveQuestionStructure;
@@ -105,6 +110,7 @@ import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperExp;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.PaperQuestionStructureInfo;
 import cn.com.qmth.examcloud.core.questions.service.bean.dto.SubjectiveQuestionStructure;
 import cn.com.qmth.examcloud.core.questions.service.bean.paper.PaperAnswerDomain;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.PaperDetailUnitDto;
 import cn.com.qmth.examcloud.core.questions.service.cache.BasePaperCache;
 import cn.com.qmth.examcloud.core.questions.service.cache.ExtractConfigPaperCache;
 import cn.com.qmth.examcloud.core.questions.service.cache.QuestionAnswerCache;
@@ -129,278 +135,279 @@ import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 @Service("paperService")
 public class PaperServiceImpl implements PaperService {
 
-    private static final Logger LOG = LoggerFactory.getLogger(PaperServiceImpl.class);
+	private static final Logger LOG = LoggerFactory.getLogger(PaperServiceImpl.class);
+	
 	@Autowired
 	private PropertyRepo propertyRepo;
-    @Autowired
-    PaperRepo paperRepo;
-
-    @Autowired
-    PaperDetailService paperDetailService;
-
-    @Autowired
-    ExamPaperRepo examPaperRepo;
-
-    @Autowired
-    PaperDetailRepo paperDetailRepo;
+	@Autowired
+	PaperRepo paperRepo;
 
-    @Autowired
-    PaperDetailUnitRepo paperDetailUnitRepo;
+	@Autowired
+	PaperDetailService paperDetailService;
 
-    @Autowired
-    QuesRepo quesRepo;
+	@Autowired
+	ExamPaperRepo examPaperRepo;
 
-    @Autowired
-    QuesBakRepo quesBakRepo;
+	@Autowired
+	PaperDetailRepo paperDetailRepo;
 
-    @Autowired
-    Gson gson;
+	@Autowired
+	PaperDetailUnitRepo paperDetailUnitRepo;
 
-    @Autowired
-    PaperDetailUnitService paperDetailUnitService;
+	@Autowired
+	QuesRepo quesRepo;
 
-    @Autowired
-    QuesService quesService;
+	@Autowired
+	QuesBakRepo quesBakRepo;
 
-    @Autowired
-    ExtractConfigService extractConfigService;
+	@Autowired
+	Gson gson;
 
-    @Autowired
-    private MongoTemplate mongoTemplate;
+	@Autowired
+	PaperDetailUnitService paperDetailUnitService;
 
-    @Autowired
-    private QuestionAudioServiceImpl questionAudioService;
+	@Autowired
+	QuesService quesService;
 
-    @Autowired
-    private SysProperty sysProperty;
+	@Autowired
+	ExtractConfigService extractConfigService;
 
-    @Autowired
-    private CoursePaperCloudService coursePaperCloudService;
+	@Autowired
+	private MongoTemplate mongoTemplate;
+	@Resource(name="mongoTemplate2")
+	private MongoTemplate mongoTemplate2;
+	@Autowired
+	private QuestionAudioServiceImpl questionAudioService;
 
-    @Autowired
-    private CourseService courseService;
+	@Autowired
+	private SysProperty sysProperty;
 
-    @Autowired
-    private ExtractConfigPaperCache extractConfigPaperCache;
+	@Autowired
+	private CoursePaperCloudService coursePaperCloudService;
 
-    @Autowired
-    private BasePaperCache basePaperCache;
+	@Autowired
+	private CourseService courseService;
 
-    @Autowired
-    private QuestionCache questionCache;
+	@Autowired
+	private ExtractConfigPaperCache extractConfigPaperCache;
 
-    @Autowired
-    private QuestionAnswerCache questionAnswerCache;
+	@Autowired
+	private BasePaperCache basePaperCache;
+	@Autowired
+	private RandomPaperQuestionService randomPaperQuestionService;
+	@Autowired
+	private RandomPaperService randomPaperService;
+	
+	@Autowired
+	private QuestionCache questionCache;
 
-    @Autowired
-    private SystemProperties systemProperties;
+	@Autowired
+	private QuestionAnswerCache questionAnswerCache;
 
-    private Random random = new Random();
+	@Autowired
+	private SystemProperties systemProperties;
 
-    public static final String TEMP_FILE_EXP = "docxExport/";
+	private Random random = new Random();
 
+	public static final String TEMP_FILE_EXP = "docxExport/";
 
-    /**
-     * 查询所有已导入试卷
-     */
-    @Override
-    public Page<Paper> getImportPapers(PaperSearchInfo paperSearchInfo, int curPage, int pageSize, UserDataRule ud) {
-    	
+	/**
+	 * 查询所有已导入试卷
+	 */
+	@Override
+	public Page<Paper> getImportPapers(PaperSearchInfo paperSearchInfo, int curPage, int pageSize, UserDataRule ud) {
 
-    	if (ud.assertEmptyQueryResult()) {
+		if (ud.assertEmptyQueryResult()) {
 			return Page.empty();
 		}
 		Query query = new Query();
-		
+
 		query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
-		
+
 		if (ud.assertNeedQueryRefIds()) {
 			query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
 		}
-        
 
-       
+		query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT));
 
-        query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT));
+		query.addCriteria(Criteria.where("course.enable").is("true"));
 
-        query.addCriteria(Criteria.where("course.enable").is("true"));
+		if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
+			query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		}
 
-        if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        }
+		if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
+			query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
+		}
 
-        if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
-            query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
-        }
+		if (StringUtils.isNoneBlank(paperSearchInfo.getName())) {
+			String paperName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getName());
+			query.addCriteria(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
+		}
 
-        if (StringUtils.isNoneBlank(paperSearchInfo.getName())) {
-            String paperName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getName());
-            query.addCriteria(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
-        }
+		if (StringUtils.isNoneBlank(paperSearchInfo.getCreator())) {
+			String creator = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getCreator());
+			query.addCriteria(Criteria.where("creator").regex(".*?\\.*" + creator + ".*"));
+		}
 
-        if (StringUtils.isNoneBlank(paperSearchInfo.getCreator())) {
-            String creator = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getCreator());
-            query.addCriteria(Criteria.where("creator").regex(".*?\\.*" + creator + ".*"));
-        }
+		if (StringUtils.isNoneBlank(paperSearchInfo.getLastModifyName())) {
+			String lastModifyName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getLastModifyName());
+			query.addCriteria(Criteria.where("lastModifyName").regex(".*?\\.*" + lastModifyName + ".*"));
+		}
 
-        if (StringUtils.isNoneBlank(paperSearchInfo.getLastModifyName())) {
-            String lastModifyName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getLastModifyName());
-            query.addCriteria(Criteria.where("lastModifyName").regex(".*?\\.*" + lastModifyName + ".*"));
-        }
+		if (StringUtils.isNotBlank(paperSearchInfo.getSpecialtyNo())) {
+			query.addCriteria(Criteria.where("specialty.code").is(paperSearchInfo.getSpecialtyNo()));
+		}
 
-        if (StringUtils.isNotBlank(paperSearchInfo.getSpecialtyNo())) {
-            query.addCriteria(Criteria.where("specialty.code").is(paperSearchInfo.getSpecialtyNo()));
-        }
+		long total = this.mongoTemplate.count(query, Paper.class);
+		if (total == 0) {
+			return Page.empty();
+		}
 
-        long total = this.mongoTemplate.count(query, Paper.class);
-        if (total == 0) {
-            return Page.empty();
-        }
+		PageRequest pageable = PageRequest.of(curPage - 1, pageSize);
+		query.with(Sort.by(Sort.Order.desc("createTime")));
+		query.skip(pageable.getOffset());
+		query.limit(pageable.getPageSize());
 
-        PageRequest pageable = PageRequest.of(curPage - 1, pageSize);
-        query.with(Sort.by(Sort.Order.desc("createTime")));
-        query.skip(pageable.getOffset());
-        query.limit(pageable.getPageSize());
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		if (CollectionUtils.isEmpty(paperList)) {
+			return Page.empty();
+		}
 
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        if (CollectionUtils.isEmpty(paperList)) {
-            return Page.empty();
-        }
+		return new PageImpl<>(paperList, pageable, total);
+	}
 
-        return new PageImpl<>(paperList, pageable, total);
-    }
-
-    /**
-     * 查询所有待审核和审核不通过的导入试卷
-     *
-     * @param paperSearchInfo
-     * @param curPage
-     * @param pageSize
-     * @return
-     */
-    public Page<Paper> getImportPapersNotSuccess(PaperSearchInfo paperSearchInfo, int curPage, int pageSize) {
-        Query query = new Query();
-        query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
-        query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT));
-        query.addCriteria(Criteria.where("course.enable").is("true"));
-        if (paperSearchInfo.getPaperStatus() != null) {
-            query.addCriteria(Criteria.where("paperStatus").is(paperSearchInfo.getPaperStatus()));
-        } else {
-            query.addCriteria(Criteria.where("paperStatus").ne(PaperStatus.PASS));
-        }
-        if (StringUtil.isNotBlank(paperSearchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        }
-        long total = this.mongoTemplate.count(query, Paper.class);
-        if (total == 0) {
-            return Page.empty();
-        }
+	/**
+	 * 查询所有待审核和审核不通过的导入试卷
+	 *
+	 * @param paperSearchInfo
+	 * @param curPage
+	 * @param pageSize
+	 * @return
+	 */
+	public Page<Paper> getImportPapersNotSuccess(PaperSearchInfo paperSearchInfo, int curPage, int pageSize) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
+		query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT));
+		query.addCriteria(Criteria.where("course.enable").is("true"));
+		if (paperSearchInfo.getPaperStatus() != null) {
+			query.addCriteria(Criteria.where("paperStatus").is(paperSearchInfo.getPaperStatus()));
+		} else {
+			query.addCriteria(Criteria.where("paperStatus").ne(PaperStatus.PASS));
+		}
+		if (StringUtil.isNotBlank(paperSearchInfo.getCourseNo())) {
+			query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		}
+		long total = this.mongoTemplate.count(query, Paper.class);
+		if (total == 0) {
+			return Page.empty();
+		}
 
-        query.limit(pageSize);
-        query.skip((curPage - 1L) * pageSize);
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        return new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
-    }
-
-    /**
-     * 根据条件查询
-     *
-     * @param paperSearchInfo
-     * @return
-     */
-    public List<Paper> getImportPapersBySearch(PaperSearchInfo paperSearchInfo) {
-        Query query = new Query();
-        query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
-        query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT.name()));
-        query.addCriteria(Criteria.where("course.enable").is("true"));
-        if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
-            query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
-        }
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        return paperList;
-    }
-
-    /**
-     * 根据条件查询
-     *
-     * @param paperSearchInfo
-     * @return
-     */
-    public List<Paper> getGenPapersBySearch(PaperSearchInfo paperSearchInfo) {
-        Query query = new Query();
-        query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
-        query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
-        query.addCriteria(Criteria.where("course.enable").is("true"));
-        query.addCriteria(Criteria.where("storage").ne(1));
-        if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        }
+		query.limit(pageSize);
+		query.skip((curPage - 1L) * pageSize);
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		return new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
+	}
 
-        if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
-            query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
-        }
+	/**
+	 * 根据条件查询
+	 *
+	 * @param paperSearchInfo
+	 * @return
+	 */
+	public List<Paper> getImportPapersBySearch(PaperSearchInfo paperSearchInfo) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
+		query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT.name()));
+		query.addCriteria(Criteria.where("course.enable").is("true"));
+		if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
+			query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
+			query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
+		}
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		return paperList;
+	}
 
-        query.with(Sort.by(Sort.Direction.DESC, "createTime"));
-
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        return paperList;
-    }
-
-    /**
-     * 保存试卷
-     *
-     * @param paperExp
-     * @return
-     */
-    public Map<String, Object> savePaper(PaperExp paperExp, User user) {
-        Map<String, Object> msgMap = new HashMap<>();
-
-        Paper oldPaper = Model.of(paperRepo.findById(paperExp.getId()));
-        if (oldPaper != null) {
-        	if (PaperType.GENERATE.equals(oldPaper.getPaperType())) {
-        		oldPaper.setAuditStatus(false);
-            }
-            String oldName = oldPaper.getName().trim();
-            oldPaper.setTitle(paperExp.getTitle());
-            oldPaper.setName(paperExp.getName().trim());
-            oldPaper.setLastModifyName(user.getDisplayName());
-            oldPaper.setExamRemark(paperExp.getExamRemark());
-            if (!oldName.equals(paperExp.getName().trim())) {// 假如改变了试卷名称
-                // 则要效验试卷名称唯一性
-                boolean existName = this.checkPaperName(paperExp.getName().trim(), user.getRootOrgId().toString());
-                if (existName) {
-                    msgMap.put("msg", "试卷名称重复,请重新命名!");
-                } else {
-                    formatPaper(oldPaper, user);
-                    paperRepo.save(oldPaper);
-                    msgMap.put("msg", "success");
-                }
-            } else {
-                formatPaper(oldPaper, user);
-                paperRepo.save(oldPaper);
-                msgMap.put("msg", "success");
-            }
-        }
+	/**
+	 * 根据条件查询
+	 *
+	 * @param paperSearchInfo
+	 * @return
+	 */
+	public List<Paper> getGenPapersBySearch(PaperSearchInfo paperSearchInfo) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
+		query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
+		query.addCriteria(Criteria.where("course.enable").is("true"));
+		query.addCriteria(Criteria.where("storage").ne(1));
+		if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
+			query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		}
+
+		if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
+			query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
+		}
+
+		query.with(Sort.by(Sort.Direction.DESC, "createTime"));
+
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		return paperList;
+	}
+
+	/**
+	 * 保存试卷
+	 *
+	 * @param paperExp
+	 * @return
+	 */
+	public Map<String, Object> savePaper(PaperExp paperExp, User user) {
+		Map<String, Object> msgMap = new HashMap<>();
+
+		Paper oldPaper = Model.of(paperRepo.findById(paperExp.getId()));
+		if (oldPaper != null) {
+			if (PaperType.GENERATE.equals(oldPaper.getPaperType())) {
+				oldPaper.setAuditStatus(false);
+			}
+			String oldName = oldPaper.getName().trim();
+			oldPaper.setTitle(paperExp.getTitle());
+			oldPaper.setName(paperExp.getName().trim());
+			oldPaper.setLastModifyName(user.getDisplayName());
+			oldPaper.setExamRemark(paperExp.getExamRemark());
+			if (!oldName.equals(paperExp.getName().trim())) {// 假如改变了试卷名称
+				// 则要效验试卷名称唯一性
+				boolean existName = this.checkPaperName(paperExp.getName().trim(), user.getRootOrgId().toString());
+				if (existName) {
+					msgMap.put("msg", "试卷名称重复,请重新命名!");
+				} else {
+					formatPaper(oldPaper, user);
+					paperRepo.save(oldPaper);
+					msgMap.put("msg", "success");
+				}
+			} else {
+				formatPaper(oldPaper, user);
+				paperRepo.save(oldPaper);
+				msgMap.put("msg", "success");
+			}
+		}
+
+		// 清除缓存
+		this.clearPaperCache(paperExp.getId());
 
-        //清除缓存
-        this.clearPaperCache(paperExp.getId());
-
-        return msgMap;
-    }
-
-    /**
-     * 查询所有已组试卷
-     *
-     * @param paperSearchInfo
-     * @param curPage
-     * @param pageSize
-     * @return
-     */
-    public Page<Paper> getGenPapers(PaperSearchInfo paperSearchInfo, int curPage, int pageSize,UserDataRule ud) {
-    	if (ud.assertEmptyQueryResult()) {
+		return msgMap;
+	}
+
+	/**
+	 * 查询所有已组试卷
+	 *
+	 * @param paperSearchInfo
+	 * @param curPage
+	 * @param pageSize
+	 * @return
+	 */
+	public Page<Paper> getGenPapers(PaperSearchInfo paperSearchInfo, int curPage, int pageSize, UserDataRule ud) {
+		if (ud.assertEmptyQueryResult()) {
 			return Page.empty();
 		}
 		Query query = new Query();
@@ -408,210 +415,216 @@ public class PaperServiceImpl implements PaperService {
 		if (ud.assertNeedQueryRefIds()) {
 			query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
 		}
-		if(paperSearchInfo.getAuditStatus()!=null) {
-			if(paperSearchInfo.getAuditStatus()) {
+		if (paperSearchInfo.getAuditStatus() != null) {
+			if (paperSearchInfo.getAuditStatus()) {
 				query.addCriteria(Criteria.where("auditStatus").ne(false));
-			}else {
+			} else {
 				query.addCriteria(Criteria.where("auditStatus").is(false));
 			}
 		}
-        query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
-        query.addCriteria(Criteria.where("storage").ne(1));
-        query.addCriteria(Criteria.where("course.enable").is("true"));
-        if (paperSearchInfo.getInUse()!=null) {
-        	if(paperSearchInfo.getInUse()==0) {
-        		query.addCriteria(Criteria.where("inUse").ne(1));
-        	}else if(paperSearchInfo.getInUse()==1) {
-        		query.addCriteria(Criteria.where("inUse").is(1));
-        	}
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getName())) {
-            String paperName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getName());
-            query.addCriteria(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
-            query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getCreator())) {
-            String creator = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getCreator());
-            query.addCriteria(Criteria.where("creator").regex(".*?\\.*" + creator + ".*"));
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getLastModifyName())) {
-            String lastModifyName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getLastModifyName());
-            query.addCriteria(Criteria.where("lastModifyName").regex(".*?\\.*" + lastModifyName + ".*"));
-        }
+		query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
+		query.addCriteria(Criteria.where("storage").ne(1));
+		query.addCriteria(Criteria.where("course.enable").is("true"));
+		if (paperSearchInfo.getInUse() != null) {
+			if (paperSearchInfo.getInUse() == 0) {
+				query.addCriteria(Criteria.where("inUse").ne(1));
+			} else if (paperSearchInfo.getInUse() == 1) {
+				query.addCriteria(Criteria.where("inUse").is(1));
+			}
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
+			query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getName())) {
+			String paperName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getName());
+			query.addCriteria(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
+			query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getCreator())) {
+			String creator = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getCreator());
+			query.addCriteria(Criteria.where("creator").regex(".*?\\.*" + creator + ".*"));
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getLastModifyName())) {
+			String lastModifyName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getLastModifyName());
+			query.addCriteria(Criteria.where("lastModifyName").regex(".*?\\.*" + lastModifyName + ".*"));
+		}
 
-        long total = this.mongoTemplate.count(query, Paper.class);
-        if (total == 0) {
-            return Page.empty();
-        }
+		long total = this.mongoTemplate.count(query, Paper.class);
+		if (total == 0) {
+			return Page.empty();
+		}
 
-        query.with(Sort.by(new Order(Direction.DESC, "createTime")));
-        query.limit(pageSize);
-        query.skip((curPage - 1L) * pageSize);
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        return new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
-    }
+		query.with(Sort.by(new Order(Direction.DESC, "createTime")));
+		query.limit(pageSize);
+		query.skip((curPage - 1L) * pageSize);
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		return new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
+	}
 
-    @Override
-    public Page<Paper> getStoragePaperPage(PaperSearchInfo paperSearchInfo, int curPage, int pageSize,UserDataRule ud) {
-    	if (ud.assertEmptyQueryResult()) {
+	@Override
+	public Page<Paper> getStoragePaperPage(PaperSearchInfo paperSearchInfo, int curPage, int pageSize,
+			UserDataRule ud) {
+		if (ud.assertEmptyQueryResult()) {
 			return Page.empty();
 		}
-    	Query query = new Query();
-        query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
-        if (ud.assertNeedQueryRefIds()) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
+		if (ud.assertNeedQueryRefIds()) {
 			query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
 		}
-        query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
-        query.addCriteria(Criteria.where("storage").is(1));
-        query.addCriteria(Criteria.where("course.enable").is("true"));
-        if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
-            query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        }
-        if (StringUtils.isNoneBlank(paperSearchInfo.getName())) {
-            String paperName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getName());
-            query.addCriteria(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
-        }
-        if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
-            query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
-        }
-        if (StringUtils.isNoneBlank(paperSearchInfo.getCreator())) {
-            String creator = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getCreator());
-            query.addCriteria(Criteria.where("creator").regex(".*?\\.*" + creator + ".*"));
-        }
-        if (StringUtils.isNoneBlank(paperSearchInfo.getLastModifyName())) {
-            String lastModifyName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getLastModifyName());
-            query.addCriteria(Criteria.where("lastModifyName").regex(".*?\\.*" + lastModifyName + ".*"));
-        }
+		query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
+		query.addCriteria(Criteria.where("storage").is(1));
+		query.addCriteria(Criteria.where("course.enable").is("true"));
+		if (StringUtils.isNotBlank(paperSearchInfo.getCourseNo())) {
+			query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		}
+		if (StringUtils.isNoneBlank(paperSearchInfo.getName())) {
+			String paperName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getName());
+			query.addCriteria(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
+		}
+		if (StringUtils.isNotBlank(paperSearchInfo.getLevel())) {
+			query.addCriteria(Criteria.where("course.level").is(paperSearchInfo.getLevel()));
+		}
+		if (StringUtils.isNoneBlank(paperSearchInfo.getCreator())) {
+			String creator = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getCreator());
+			query.addCriteria(Criteria.where("creator").regex(".*?\\.*" + creator + ".*"));
+		}
+		if (StringUtils.isNoneBlank(paperSearchInfo.getLastModifyName())) {
+			String lastModifyName = CommonUtils.escapeExprSpecialWord(paperSearchInfo.getLastModifyName());
+			query.addCriteria(Criteria.where("lastModifyName").regex(".*?\\.*" + lastModifyName + ".*"));
+		}
 
-        long total = this.mongoTemplate.count(query, Paper.class);
-        if (total == 0) {
-            return Page.empty();
-        }
+		long total = this.mongoTemplate.count(query, Paper.class);
+		if (total == 0) {
+			return Page.empty();
+		}
 
-        query.with(Sort.by(new Order(Direction.DESC, "createTime")));
-        query.limit(pageSize);
-        query.skip((curPage - 1L) * pageSize);
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        return new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
-    }
-
-    /**
-     * 查询考试试卷
-     *
-     * @param id
-     * @param courseCode
-     * @param groupCode
-     * @return
-     */
-    public List<Paper> listExamPapers(long id, String courseCode, String groupCode) {
-        List<Paper> papers = new ArrayList<>();
-        ExamPaper examPaper = new ExamPaper();
-        examPaper.setExamId(id);
-        examPaper.setCourseCode(courseCode);
-        examPaper.setGroupCode(groupCode);
-        Example<ExamPaper> example = Example.of(examPaper);
-        List<ExamPaper> examPapers = examPaperRepo.findAll(example);
-        for (ExamPaper ePaper : examPapers) {
-            Paper paper = Model.of(paperRepo.findById(ePaper.getPaper().getId()));
-            papers.add(paper);
-        }
-        return papers;
-    }
-
-    /**
-     * 设置考试试卷
-     *
-     * @param examId
-     * @param courseCode
-     * @param groupCode
-     * @param paperId
-     * @return
-     */
-    public void joinToExamPaper(long examId, String courseCode, String groupCode, String paperId) {
-        ExamPaper examPaper = new ExamPaper();
-        examPaper.setExamId(examId);
-        examPaper.setGroupCode(groupCode);
-        examPaper.setCourseCode(courseCode);
-        // examPaper.setPaperId(paperId);
-        examPaperRepo.save(examPaper);
-    }
-
-    public void releaseExamPaper(long examId, String courseCode, String groupCode, String paperId) {
-        ExamPaper examPaper = new ExamPaper();
-        examPaper.setExamId(examId);
-        examPaper.setGroupCode(groupCode);
-        examPaper.setCourseCode(courseCode);
-        // examPaper.setPaperId(paperId);
-        examPaperRepo.delete(examPaper);
-    }
-
-    public Set<String> listGroupCodes(long examId, String courseCode) {
-        Set<String> groupSet = new HashSet<>();
-        ExamPaper examPaper = new ExamPaper();
-        examPaper.setExamId(examId);
-        examPaper.setCourseCode(courseCode);
-        List<ExamPaper> examPapers = examPaperRepo.findAll(Example.of(examPaper));
-        for (ExamPaper expaper : examPapers) {
-            groupSet.add(expaper.getGroupCode());
-        }
-        return groupSet;
-    }
-
-    public void deletGroupCode(long examId, String courseCode, String groupCode) {
-        ExamPaper examPaper = new ExamPaper();
-        examPaper.setExamId(examId);
-        examPaper.setCourseCode(courseCode);
-        examPaper.setCourseCode(courseCode);
-        examPaperRepo.delete(examPaper);
-    }
-
-    /**
-     * 根据试卷ID获取试卷下面的大题
-     *
-     * @param id
-     * @return
-     */
-    public List<PaperDetail> findPaperDetailsById(String id) {
-        return paperDetailService.getPaperDetailsByPaper(Model.of(paperRepo.findById(id)));
-    }
-
-    /**
-     * 批量删除试卷
-     *
-     * @param paperIds
-     */
-    public void deletePapers(List<String> paperIds, User user) {
-        List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
-        if (papers.get(0).getPaperType() == PaperType.IMPORT) {
-            List<Question> quesList = new ArrayList<>();
-            for (Paper paper : papers) {
-                List<PaperDetailUnit> paperUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
-                for (PaperDetailUnit pdu : paperUnits) {
-                    if (pdu.getQuestion() != null) {
-                        quesList.add(pdu.getQuestion());
-                    }
-                }
-            }
-            List<PaperDetailUnit> allUnits = paperDetailUnitRepo.findByQuestionIn(quesList);
-            for (PaperDetailUnit pdu : allUnits) {
-                if (pdu.getPaper() != null && pdu.getPaper().getPaperType() == PaperType.GENERATE) {
+		query.with(Sort.by(new Order(Direction.DESC, "createTime")));
+		query.limit(pageSize);
+		query.skip((curPage - 1L) * pageSize);
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		return new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
+	}
+
+	/**
+	 * 查询考试试卷
+	 *
+	 * @param id
+	 * @param courseCode
+	 * @param groupCode
+	 * @return
+	 */
+	public List<Paper> listExamPapers(long id, String courseCode, String groupCode) {
+		List<Paper> papers = new ArrayList<>();
+		ExamPaper examPaper = new ExamPaper();
+		examPaper.setExamId(id);
+		examPaper.setCourseCode(courseCode);
+		examPaper.setGroupCode(groupCode);
+		Example<ExamPaper> example = Example.of(examPaper);
+		List<ExamPaper> examPapers = examPaperRepo.findAll(example);
+		for (ExamPaper ePaper : examPapers) {
+			Paper paper = Model.of(paperRepo.findById(ePaper.getPaper().getId()));
+			papers.add(paper);
+		}
+		return papers;
+	}
+
+	/**
+	 * 设置考试试卷
+	 *
+	 * @param examId
+	 * @param courseCode
+	 * @param groupCode
+	 * @param paperId
+	 * @return
+	 */
+	public void joinToExamPaper(long examId, String courseCode, String groupCode, String paperId) {
+		ExamPaper examPaper = new ExamPaper();
+		examPaper.setExamId(examId);
+		examPaper.setGroupCode(groupCode);
+		examPaper.setCourseCode(courseCode);
+		// examPaper.setPaperId(paperId);
+		examPaperRepo.save(examPaper);
+	}
+
+	public void releaseExamPaper(long examId, String courseCode, String groupCode, String paperId) {
+		ExamPaper examPaper = new ExamPaper();
+		examPaper.setExamId(examId);
+		examPaper.setGroupCode(groupCode);
+		examPaper.setCourseCode(courseCode);
+		// examPaper.setPaperId(paperId);
+		examPaperRepo.delete(examPaper);
+	}
+
+	public Set<String> listGroupCodes(long examId, String courseCode) {
+		Set<String> groupSet = new HashSet<>();
+		ExamPaper examPaper = new ExamPaper();
+		examPaper.setExamId(examId);
+		examPaper.setCourseCode(courseCode);
+		List<ExamPaper> examPapers = examPaperRepo.findAll(Example.of(examPaper));
+		for (ExamPaper expaper : examPapers) {
+			groupSet.add(expaper.getGroupCode());
+		}
+		return groupSet;
+	}
+
+	public void deletGroupCode(long examId, String courseCode, String groupCode) {
+		ExamPaper examPaper = new ExamPaper();
+		examPaper.setExamId(examId);
+		examPaper.setCourseCode(courseCode);
+		examPaper.setCourseCode(courseCode);
+		examPaperRepo.delete(examPaper);
+	}
+
+	/**
+	 * 根据试卷ID获取试卷下面的大题
+	 *
+	 * @param id
+	 * @return
+	 */
+	public List<PaperDetail> findPaperDetailsById(String id) {
+		return paperDetailService.getPaperDetailsByPaper(Model.of(paperRepo.findById(id)));
+	}
+
+	/**
+	 * 批量删除试卷
+	 *
+	 * @param paperIds
+	 */
+	@Override
+	public void deletePapers(List<String> paperIds, User user) {
+		List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
+		if (papers.get(0).getPaperType() == PaperType.IMPORT) {
+			List<Question> quesList = new ArrayList<>();
+			for (Paper paper : papers) {
+				List<PaperDetailUnit> paperUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+				List<String> questionIds=paperUnits.stream().map(e->e.getQuestion().getId()).collect(Collectors.toList());
+				if(randomPaperQuestionService.existQuestion(questionIds)) {
+					throw new StatusException("试卷[" + paper.getName() + "]中有试题被抽题模板使用,不能删除");
+				}
+				for (PaperDetailUnit pdu : paperUnits) {
+					if (pdu.getQuestion() != null) {
+						quesList.add(pdu.getQuestion());
+					}
+				}
+			}
+			List<PaperDetailUnit> allUnits = paperDetailUnitRepo.findByQuestionIn(quesList);
+			for (PaperDetailUnit pdu : allUnits) {
+				if (pdu.getPaper() != null && pdu.getPaper().getPaperType() == PaperType.GENERATE) {
 //                    msg = "待删除试卷中有试题被组卷使用,不能删除!";
 //                    msgMap.put("msg", msg);
 //                    msgMap.put("paperName", pdu.getPaper().getName());
 //                    return msgMap;
-                    throw new StatusException("试卷["+pdu.getPaper().getName()+"]中有试题被组卷使用,不能删除");
-                }
-            }
-            //删除音频
-            questionAudioService.deleteAudio(quesList);
-            quesRepo.deleteAll(quesList);
-        } else if (papers.get(0).getPaperType() == PaperType.GENERATE) {
-            for (Paper paper : papers) {
+					throw new StatusException("试卷[" + pdu.getPaper().getName() + "]中有试题被组卷使用,不能删除");
+				}
+			}
+			// 删除音频
+			questionAudioService.deleteAudio(quesList);
+			quesRepo.deleteAll(quesList);
+		} else if (papers.get(0).getPaperType() == PaperType.GENERATE) {
+			for (Paper paper : papers) {
 //                List<String> examPaperIds = extractConfigService.getExamPaperId(paper.getCourseNo(), paper.getOrgId());
 //                if (examPaperIds != null && examPaperIds.contains(paper.getId())) {
 //                    msg = "待删除试卷有被调卷规则使用,不能删除!";
@@ -619,1858 +632,2028 @@ public class PaperServiceImpl implements PaperService {
 //                    msgMap.put("paperName", paper.getName());
 //                    return msgMap;
 //                }
-                if(paper.getInUse()!=null&&paper.getInUse()==1) {
-		            throw new StatusException("试卷["+paper.getName()+"]已调用,不能删除");
-        		}
-            }
-        }
-        paperDetailService.deletePaperDetailsByPapers(papers);
-        paperRepo.deleteAll(papers);
+				if (paper.getInUse() != null && paper.getInUse() == 1) {
+					throw new StatusException("试卷[" + paper.getName() + "]已调用,不能删除");
+				}
+				if(randomPaperService.existPaper(Long.valueOf(paper.getCourse().getId()),paper.getId())) {
+					throw new StatusException("试卷[" + paper.getName() + "]被抽题模板使用,不能删除");
+				}
+			}
+		}
+		paperDetailService.deletePaperDetailsByPapers(papers);
+		paperRepo.deleteAll(papers);
 
-        for (String paperId : paperIds) {
-            //清除缓存
-            this.clearPaperCache(paperId);
-        }
+		for (String paperId : paperIds) {
+			// 清除缓存
+			this.clearPaperCache(paperId);
+		}
 
-        for (Paper paper : papers) {
-            StringBuilder paperInfo = new StringBuilder();
-            paperInfo.append("课程:" + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")");
-            paperInfo.append(" 试卷名称:" + paper.getName());
-            if (PaperType.IMPORT.equals(paper.getPaperType())) {
-                ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE46.getDesc(), paperInfo.toString()));
-            } else {
-                ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE47.getDesc(), paperInfo.toString()));
+		for (Paper paper : papers) {
+			StringBuilder paperInfo = new StringBuilder();
+			paperInfo.append("课程:" + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")");
+			paperInfo.append(" 试卷名称:" + paper.getName());
+			if (PaperType.IMPORT.equals(paper.getPaperType())) {
+				ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+						AdminOperateType.TYPE46.getDesc(), paperInfo.toString()));
+			} else {
+				ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+						AdminOperateType.TYPE47.getDesc(), paperInfo.toString()));
 
-            }
-        }
-    }
-
-    /**
-     * 批量通过试卷
-     *
-     * @param paperIds
-     */
-    public void passPapers(List<String> paperIds) {
-        List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
-        papers.stream().forEach(paper -> {
-            paper.setPaperStatus(PaperStatus.PASS);
-        });
-        paperRepo.saveAll(papers);
-    }
-
-    @Override
-    public void updatePapersStorage(List<String> paperIds, int storage, String orgId) {
-        List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
-        List<Paper> papersList = papers.stream().filter(p -> orgId.equals(p.getOrgId())).collect(Collectors.toList());
-        papersList.stream().forEach(paper -> {
-            paper.setStorage(storage);
-        });
-        if (papersList.size() > 0) {
-            paperRepo.saveAll(papersList);
-        }
-    }
-
-    /**
-     * 批量不通过试卷
-     *
-     * @param paperIds
-     */
-    public void noPassPapers(List<String> paperIds) {
-        List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
-        papers.stream().forEach(paper -> {
-            paper.setPaperStatus(PaperStatus.NOPASS);
-        });
-        paperRepo.saveAll(papers);
-    }
-
-    /**
-     * 批量待审核试卷
-     *
-     * @param paperIds
-     */
-    public void backPapers(List<String> paperIds) {
-        List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
-        papers.stream().forEach(paper -> {
-            paper.setPaperStatus(PaperStatus.DRAFT);
-        });
-        paperRepo.saveAll(papers);
-    }
-
-    /**
-     * 初始化导出试卷DTO
-     *
-     * @param id
-     * @return
-     */
-    public PaperExp getPaperDto(String id) {
-        Paper paper = Model.of(paperRepo.findById(id));
-        // 创建paperDto
-        PaperExp paperExp = new PaperExp();
-        BeanUtils.copyProperties(paper, paperExp);
-        paperExp.setCourse(paper.getCourse());
-        // 获取大题
-        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
-        List<PaperDetailExp> paperDetailExps = new ArrayList<>();
-        for (PaperDetail paperDetail : paperDetails) {
-            PaperDetailExp paperDetailExp = new PaperDetailExp();
-            BeanUtils.copyProperties(paperDetail, paperDetailExp);
-            paperDetailExps.add(paperDetailExp);
-        }
-        // 封装小题
-        for (int i = 0; i < paperDetailExps.size(); i++) {
-            List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperDetailOrderByNumber(paperDetails.get(i));
-            if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
-                List<PaperDetailUnitExp> paperDetailUnitExps = new ArrayList<>();
-                for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-                    PaperDetailUnitExp paperDetailUnitExp = new PaperDetailUnitExp();
-                    BeanUtils.copyProperties(paperDetailUnit, paperDetailUnitExp);
-                    fillProperty(paperDetailUnitExp.getQuestion());
-                    if(!CollectionUtils.isEmpty(paperDetailUnitExp.getQuestion().getSubQuestions())) {
-                    	for(Question subque:paperDetailUnitExp.getQuestion().getSubQuestions()) {
-                    		fillProperty(subque);
-                    	}
-                    }
-                    paperDetailUnitExps.add(paperDetailUnitExp);
-                }
-                // 选择题,套题下选择题 选项顺序重新排列
-                reorderChoicequestionOption(paperDetailUnitExps);
-                paperDetailExps.get(i).setPaperDetailUnits(paperDetailUnitExps);
-            } else {
-                paperDetailExps.get(i).setUnitCount(0);
-            }
-        }
-        paperExp.setPaperDetails(paperDetailExps);
-        // 初始化试卷内容
-        initPaper(paperExp);
-        return paperExp;
-    }
-
-    /**
-     * 重新对选择题option进行排序(多选、单选、套题下选择题)
-     */
-    public void reorderChoicequestionOption(List<PaperDetailUnitExp> paperDetailUnitExps) {
-        for (PaperDetailUnitExp paperDetailUnitExp : paperDetailUnitExps) {
-            String optionOrder = paperDetailUnitExp.getOptionOrder();
-            if (StringUtil.isNotBlank(optionOrder)) {
-                Question question = paperDetailUnitExp.getQuestion();
-                if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-                        || question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                    question.setQuesOptions(reorderOptionCore(question.getQuesOptions(), optionOrder));
-                }
-                if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                    List<Question> subQuestions = question.getSubQuestions();
-                    int index = 0;
-                    for (int k = 0; k < subQuestions.size(); k++) {
-                        Question subQuestion = subQuestions.get(k);
-                        if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
-                                || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                            subQuestion.setQuesOptions(
-                                    reorderOptionCore(subQuestion.getQuesOptions(), optionOrder.split(";")[index]));
-                            index++;
-                        }
-                    }
-                }
-            }
-        }
-    }
+			}
+		}
+	}
+	private List<PaperDetailUnitDto> findUnitByPaperId(String paperId) {
+		Object id ;
+		if (paperId.length() > 24) {
+			id=paperId;
+		} else {
+			id=new ObjectId(paperId);
+		}
+		Query query = new Query();
+		query.addCriteria(Criteria.where("paper.$id").is(id));
+		List<PaperDetailUnitDto> units = this.mongoTemplate2.find(query, PaperDetailUnitDto.class, "paperDetailUnit");
+		return units;
+	}
+	private boolean existGenerateQuestion(List<String> questionIds) {
+		List<Object> ids = new ArrayList<>();
+		for (String pid : questionIds) {
+			if (pid.length() > 24) {
+				ids.add(pid);
+			} else {
+				ids.add(new ObjectId(pid));
+			}
+		}
+		Query query = new Query();
+		query.addCriteria(Criteria.where("question.$id").in(ids));
+		query.addCriteria(Criteria.where("paperType").is(PaperType.GENERATE.name()));
+		long count=mongoTemplate.count(query,"paperDetailUnit");
+		return count>0;
+	}
+	private void removeByIds(List<String> stringIds,String collectionName) {
+		List<Object> ids = new ArrayList<>();
+		for (String pid : stringIds) {
+			if (pid.length() > 24) {
+				ids.add(pid);
+			} else {
+				ids.add(new ObjectId(pid));
+			}
+		}
+		Query query = new Query();
+		query.addCriteria(Criteria.where("_id").in(ids));
+		mongoTemplate.remove(query,collectionName);
+	}
+	private void removeDetailByPaperIds(List<String> paperIds) {
+		List<Object> ids = new ArrayList<>();
+		for (String pid : paperIds) {
+			if (pid.length() > 24) {
+				ids.add(pid);
+			} else {
+				ids.add(new ObjectId(pid));
+			}
+		}
+		Query query = new Query();
+		query.addCriteria(Criteria.where("paper.$id").in(ids));
+		mongoTemplate.remove(query,"paperDetail");
+	}
+	@Override
+	public void deletePapersPlus(List<String> paperIds, User user) {
+		List<Paper> papers = paperRepo.findByIdIn(paperIds);
+		List<PaperDetailUnitDto> unitList=new ArrayList<>();
+		if (papers.get(0).getPaperType() == PaperType.IMPORT) {
+			List<String> quesList = new ArrayList<>();
+			for (Paper paper : papers) {
+				List<PaperDetailUnitDto> paperUnits = findUnitByPaperId(paper.getId());
+				if(CollectionUtils.isEmpty(paperUnits)) {
+					continue;
+				}
+				List<String> questionIds=paperUnits.stream().map(e->e.getQuestion().getId()).collect(Collectors.toList());
+				if(existGenerateQuestion(questionIds)) {
+					throw new StatusException("试卷[" + paper.getName() + "]中有试题被组卷使用,不能删除");
+				}
+				if(randomPaperQuestionService.existQuestion(questionIds)) {
+					throw new StatusException("试卷[" + paper.getName() + "]中有试题被抽题模板使用,不能删除");
+				}
+				unitList.addAll(paperUnits);
+				quesList.addAll(questionIds);
+			}
+			if(CollectionUtils.isNotEmpty(quesList)) {
+				questionAudioService.deleteAudioByQuestionId(quesList);
+				removeByIds(quesList, "question");
+			}
+		} else if (papers.get(0).getPaperType() == PaperType.GENERATE) {
+			for (Paper paper : papers) {
+				if (paper.getInUse() != null && paper.getInUse() == 1) {
+					throw new StatusException("试卷[" + paper.getName() + "]已调用,不能删除");
+				}
+				if(randomPaperService.existPaper(Long.valueOf(paper.getCourse().getId()),paper.getId())) {
+					throw new StatusException("试卷[" + paper.getName() + "]被抽题模板使用,不能删除");
+				}
+				List<PaperDetailUnitDto> paperUnits = findUnitByPaperId(paper.getId());
+				if(CollectionUtils.isEmpty(paperUnits)) {
+					continue;
+				}
+				unitList.addAll(paperUnits);
+			}
+		}
+		List<String> unitids=unitList.stream().map(e->e.getId()).collect(Collectors.toList());
+		if(CollectionUtils.isNotEmpty(unitids)) {
+			removeByIds(unitids, "paperDetailUnit");
+		}
+		removeDetailByPaperIds(paperIds);
+		removeByIds(paperIds, "paper");
 
-    private List<QuesOption> reorderOptionCore(List<QuesOption> quesOptions, String optionOrder) {
-        List<QuesOption> newQuesOptions = new ArrayList<>();
-        if (StringUtil.isBlank(optionOrder) || quesOptions.isEmpty()) {
-            return null;
-        }
-        String[] optionOrderArr = optionOrder.split(",");
-        for (int j = 0; j < optionOrderArr.length; j++) {
-            for (int k = 0; k < quesOptions.size(); k++) {
-                if (optionOrderArr[j].equals(quesOptions.get(k).getNumber())) {
-                    newQuesOptions.add(quesOptions.get(k));
-                }
-            }
-        }
-        quesOptions = null;
-        return newQuesOptions;
-    }
-
-    /**
-     * 初始化试卷内容(增加序号)
-     *
-     * @param paperExp
-     */
-    public void initPaper(PaperExp paperExp) {
-        if (paperExp.getPaperDetails() == null || paperExp.getPaperDetails().size() == 0) {
-            return;
-        }
-        int mainNum = 0;
-        List<PaperDetailExp> paperDetailExpList = paperExp.getPaperDetails();
-        for (PaperDetailExp paperDetail : paperDetailExpList) {
-            // 大题序号
-            paperDetail.setNumber(++mainNum);
-            paperDetail.setCnNum(CommonUtils.toCHNum(paperDetail.getNumber()));
-            if (paperDetail != null && paperDetail.getPaperDetailUnits() != null && paperDetail.getPaperDetailUnits().size() > 0) {
-                for (PaperDetailUnitExp paperDetailUnit : paperDetail.getPaperDetailUnits()) {
-                    Question question = paperDetailUnit.getQuestion();
-                    if (question != null) {
-                        if (question.getHasAudio() != null && question.getHasAudio()) {
-                            paperExp.setHasAudio(true);    //设置试卷含有音频
-                        }
-                        quesService.formatQuesUnit(question);
-                        List<Question> subQuesList = question.getSubQuestions();
-                        // 套题序号
-                        if (subQuesList != null && subQuesList.size() > 0) {
-                            int index = 0;
-                            for (Question subQues : subQuesList) {
-                                Map<String, String> params = new HashMap<>();
-                                params.put("number", String.valueOf(++index));
-                                subQues.setQuesParams(params);
-                                quesService.formatQuesUnit(subQues);
-                            }
-                            String quesBodyHtml = CommonUtils.relaceQuestionIdx(question.getQuesBody(), 0);
-                            question.setQuesBody(quesBodyHtml);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * 格式化查询条件
-     *
-     * @param paperSearchInfo
-     */
-    public void formatPaperSearchInfo(PaperSearchInfo paperSearchInfo) {
-        if (StringUtils.isEmpty(paperSearchInfo.getCourseNo())) {
-            paperSearchInfo.setCourseNo(null);
-        }
-        if (StringUtils.isEmpty(paperSearchInfo.getCreateTime())) {
-            paperSearchInfo.setCreateTime(null);
-        }
-        if (StringUtils.isEmpty(paperSearchInfo.getCreator())) {
-            paperSearchInfo.setCreator(null);
-        }
-        if (StringUtils.isEmpty(paperSearchInfo.getName())) {
-            paperSearchInfo.setName(null);
-        }
-    }
-
-    /**
-     * 1.重新设置小题number,并返回试卷总分
-     * 2.重新计算大题的总分和小题总数
-     * 3.重新保存试卷大题数量、小题数量、试卷总分等信息
-     *
-     * @param paper
-     */
-    public void formatPaper(Paper paper, User user) {
-        double paperTotalScore = reSetPaperDetailUnit(paper);
-        paper.setDifficultyDegree(reSetDifficulty(paper, paperTotalScore));
-        Map<String, Object> paperInfoMap = reSetPaperDetail(paper);
-        reSetPaper(paper, user, paperInfoMap, paperTotalScore);
-    }
-
-    /**
-     * 计算试卷难度
-     *
-     * @param paper
-     * @param paperTotalScore
-     * @return
-     */
-
-    @SuppressWarnings("unused")
-    public Double reSetDifficulty(Paper paper, double paperTotalScore) {
-        Double sum = 0.0;
-        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
-        if (paperDetails != null && paperDetails.size() > 0) {
-            for (PaperDetail paperDetail : paperDetails) {
-                //获取每个大题下面的所有小题
-                List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperDetailOrderByNumber(paperDetail);
-                if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
-                    for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-                        //如果为套题,重新计算难度
-                        if (paperDetailUnit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                            //重新计算套题的难度,公开度
-                            boolean publicity = false;
-                            double totalSum = 0d;
-                            double totalDou = 0d;
-                            for (int i = 0; i < paperDetailUnit.getQuestion().getSubQuestions().size(); i++) {
-                                Question subQuestion = paperDetailUnit.getQuestion().getSubQuestions().get(i);
-                                //设置公开度
-                                if (subQuestion.getPublicity() == null) {
-                                    publicity = true;
-                                } else {
-                                    if (subQuestion.getPublicity()) {
-                                        publicity = true;
-                                    }
-                                }
-                                if (subQuestion.getDifficultyDegree() == null) {
-                                    subQuestion.setDifficultyDegree(0.5);
-                                }
-                                totalSum = subQuestion.getDifficultyDegree() * paperDetailUnit.getSubScoreList().get(i) + totalSum;
-                                totalDou = paperDetailUnit.getSubScoreList().get(i) + totalDou;
-                            }
-
-                            BigDecimal b;
-                            if (totalDou != 0d) {
-                                b = BigDecimal.valueOf(totalSum / totalDou);
-                            } else {
-                                b = BigDecimal.valueOf(0d);
-                            }
-
-                            Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
-                            paperDetailUnit.getQuestion().setDifficultyDegree(difficulty);
-                            paperDetailUnit.setScore(totalDou);
-                        }
-                        //旧题没有难度值,需要重新赋值
-                        if (paperDetailUnit.getQuestion().getDifficultyDegree() == null) {
-                            paperDetailUnit.getQuestion().setDifficultyDegree(0.5);
-                        }
-                        if (paperDetailUnit.getScore() == null) {
-                            paperDetailUnit.setScore(0d);
-                        }
-                        sum = paperDetailUnit.getScore() * paperDetailUnit.getQuestion().getDifficultyDegree() + sum;
-                    }
-                }
-            }
-            if (paperTotalScore < 0.1) {
-                return 0.0;
-            }
-            BigDecimal b = BigDecimal.valueOf(sum / paperTotalScore);
-            Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
-            return difficulty;
-        }
-        return 0.0;
-    }
-
-    /**
-     * 重新设置小题number,并返回试卷总分
-     *
-     * @param paper
-     * @return
-     */
-    private double reSetPaperDetailUnit(Paper paper) {
-        List<PaperDetailUnit> paperDetailUnitAll = paperDetailUnitService.findByPaperAndSort(paper);
-        double totalScore = 0;
-        for (int i = 0; i < paperDetailUnitAll.size(); i++) {
-            PaperDetailUnit unit = paperDetailUnitAll.get(i);
-            if (unit.getScore() != null) {
-                totalScore += unit.getScore();
-            }
-            unit.setNumber(i + 1);
-        }
-        paperDetailUnitRepo.saveAll(paperDetailUnitAll);
-        totalScore = CommonUtils.formatDouble(totalScore);
-        return totalScore;
-    }
-
-    /**
-     * 重新计算大题的总分和小题总数
-     *
-     * @param paper
-     */
-    private Map<String, Object> reSetPaperDetail(Paper paper) {
-        Map<String, Object> paperInfoMap = new HashMap<>();
-        int allQuesCount = 0;
-        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
-        // 计算各大题总分和小题数量
-        for (PaperDetail paperDetail : paperDetails) {
-            List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperDetailOrderByNumber(paperDetail);
-            if (paperDetailUnits.size() > 0) {
-                int count = 0;
-                double score = 0;
-                int nestQusNum = 0;
-                for (PaperDetailUnit unit : paperDetailUnits) {
-                    if (unit.getScore() != null) {
-                        score += unit.getScore();
-                    }
-                    if (unit.getQuestion() != null
-                            && unit.getQuestion().getSubQuestions() != null
-                            && unit.getQuestion().getSubQuestions().size() > 0) {
-                        nestQusNum += unit.getQuestion().getSubQuestions().size() - 1;
-                    }
-                }
-                count = paperDetailUnits.size() + nestQusNum;
-                score = CommonUtils.formatDouble(score);
-                paperDetail.setScore(score);
-                paperDetail.setUnitCount(count);
-                allQuesCount += count;
-            } else {
-                paperDetail.setScore(0d);
-            }
-        }
-        paperDetailRepo.saveAll(paperDetails);
-        paperInfoMap.put("allQuesCount", allQuesCount);
-        paperInfoMap.put("paperDetails", paperDetails);
-        return paperInfoMap;
-    }
-
-    /**
-     * 重新设置试卷大题数量、小题数量、试卷总分等属性
-     *
-     * @param paper
-     * @param user
-     * @param paperInfoMap
-     * @param paperTotalScore
-     */
-    @SuppressWarnings("unchecked")
-    private void reSetPaper(Paper paper, User user, Map<String, Object> paperInfoMap, double paperTotalScore) {
-        List<PaperDetail> paperDetails = (List<PaperDetail>) paperInfoMap.get("paperDetails");
-        paper.setPaperDetailCount(paperDetails.size());//设置大题数量
-        paper.setUnitCount(Integer.parseInt(paperInfoMap.get("allQuesCount") + ""));//设置小题数量
-        paper.setTotalScore(paperTotalScore);//设置试卷总分
-        if (user != null) {
-            paper.setLastModifyName(user.getDisplayName());
-        }
-        paperRepo.save(paper);
-
-        //清除缓存
-        this.clearPaperCache(paper.getId());
-    }
-
-    /**
-     * 先备份准备删掉的试题,然后再删掉
-     *
-     * @param questionId
-     * @return
-     */
-    public List<String> deleteImportQuestionById(String detailUnitId, String questionId, User user) {
-        PaperDetailUnit paperDetailUnit = null;
-        if (detailUnitId != null) {
-            paperDetailUnit = Model.of(paperDetailUnitRepo.findById(detailUnitId));
-            Paper paper=paperDetailUnit.getPaper();
-            if(paper.getInUse()!=null&&paper.getInUse()==1) {
-    			throw new StatusException("500", "试卷已调用");
-    		}
-        }
-        Question ques = Model.of(quesRepo.findById(questionId));
-
-        List<PaperDetailUnit> pdus = CommonUtils.toList(paperDetailUnitRepo.findByQuestion(ques));
-        List<String> paperNames = new ArrayList<>();
-
-        // 需要删除的小题
-        List<PaperDetailUnit> needPdus = new ArrayList<>();
-
-        List<Paper> papers = new ArrayList<>();
-        for (PaperDetailUnit pdu : pdus) {
-            if (pdu.getPaper() != null) {
-                if (!papers.contains(pdu.getPaper())) {
-                    papers.add(pdu.getPaper());
-                }
-                if (PaperType.GENERATE == pdu.getPaper().getPaperType()) {
-                    paperNames.add(pdu.getPaper().getName());
-                }
-            }
-        }
-        String changInfo = null;
-        if (paperNames.size() == 0) {
-            needPdus.addAll(pdus);// 此试题没有被组卷调用,则可以删除此试题
-            paperDetailUnitRepo.deleteAll(needPdus);
-            quesBakRepo.save(BeanCopierUtil.copyProperties(ques, QuestionBak.class));
-            quesRepo.delete(ques);
-
-            for (Paper paper : papers) {
-                if (paperDetailUnit != null && paperDetailUnit.getPaper().getId().equals(paper.getId())) {
-                    Double total = paper.getTotalScore();
-                    Integer dc = paper.getPaperDetailCount();
-                    Integer uc = paper.getUnitCount();
-                    formatPaper(paper, user);
-                    changInfo = PaperUtil.getPaperChangeInfo(total, dc, uc, paper);
-                } else {
-                    formatPaper(paper, user);
-                }
-            }
-        }
+		for (String paperId : paperIds) {
+			// 清除缓存
+			this.clearPaperCache(paperId);
+		}
 
-        if (paperDetailUnit != null) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("课程:" + paperDetailUnit.getPaper().getCourse().getName() + "(" + paperDetailUnit.getPaper().getCourse().getCode() + ")");
-            sb.append(" 试卷名称:" + paperDetailUnit.getPaper().getName());
-            sb.append(" 第" + paperDetailUnit.getNumber() + "小题 ");
-            sb.append(changInfo);
-            ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE48.getDesc(), sb.toString()));
-        }
-        return paperNames;
-    }
-
-    /**
-     * 向试卷中插入一个试题
-     *
-     * @param paperId
-     * @param paperDetailId
-     * @return
-     */
-    public Paper insertQuestionToPaper(String paperId, String paperDetailId, Question question, User user) {
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        question.setOrgId(user.getRootOrgId().toString());
-        question.setCourse(paper.getCourse());
-        question.setCourseNo(paper.getCourse().getCode());//必须设置,因为部分方法需要courseNo作为查询条件
-        question = quesService.saveQues(question);
-
-        PaperDetail paperDetail = Model.of(paperDetailRepo.findById(paperDetailId));
-        PaperDetailUnit paperDetailUnit = new PaperDetailUnit(paper, paperDetail, question);
-        paperDetailUnit.setPaperType(PaperType.IMPORT);
-        PaperDetailUnit paperDetailUnit2 = paperDetailUnitService.findTopOrderByNumber(paperDetail, "DESC");
-        if (paperDetailUnit2 == null) {
-            paperDetailUnit.setNumber(1);
-        } else {
-            paperDetailUnit.setNumber(paperDetailUnit2.getNumber());//设置number为大题的最大题号
-        }
-        paperDetailUnitRepo.save(paperDetailUnit);
-        formatPaper(paper, user);
-        return paper;
-    }
-
-    /**
-     * 获取试题所在的试卷名称
-     *
-     * @param questionId
-     * @return
-     */
-    public List<String> getPaperNamesByQuestionId(String questionId) {
-        List<String> paperNames = new ArrayList<>();
-        List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByQuestion(Model.of(quesRepo.findById(questionId)));
-        for (PaperDetailUnit pdu : pdus) {
-            paperNames.add(pdu.getPaper().getName());
-        }
-        return paperNames;
+		for (Paper paper : papers) {
+			StringBuilder paperInfo = new StringBuilder();
+			paperInfo.append("课程:" + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")");
+			paperInfo.append(" 试卷名称:" + paper.getName());
+			if (PaperType.IMPORT.equals(paper.getPaperType())) {
+				ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+						AdminOperateType.TYPE46.getDesc(), paperInfo.toString()));
+			} else {
+				ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+						AdminOperateType.TYPE47.getDesc(), paperInfo.toString()));
 
-    }
+			}
+		}
+	}
 
-    public Page<Question> listQuestionforSelect(String paperId, int curPage, int pageSize, QuesStructType quesType, User user, String quesBody,UserDataRule ud) {
-        Set<String> selectedIds = new HashSet<>();
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
-        for (PaperDetailUnit pdu : pdus) {
-            selectedIds.add(pdu.getQuestion().getId());
-        }
-        return quesService.findByIdExclude(selectedIds, paper.getCourseNo(), quesType, curPage, pageSize, user.getRootOrgId(), quesBody,ud);
-    }
+	/**
+	 * 批量通过试卷
+	 *
+	 * @param paperIds
+	 */
+	public void passPapers(List<String> paperIds) {
+		List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
+		papers.stream().forEach(paper -> {
+			paper.setPaperStatus(PaperStatus.PASS);
+		});
+		paperRepo.saveAll(papers);
+	}
 
-    @SuppressWarnings("unused")
-    public Paper selectQuestionsToPaper(String paperId, String paperDetailId, List<Question> questions, User user) {
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        if(paper.getInUse()!=null&&paper.getInUse()==1) {
-			throw new StatusException("500", "试卷已调用");
+	@Override
+	public void updatePapersStorage(List<String> paperIds, int storage, String orgId) {
+		List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
+		List<Paper> papersList = papers.stream().filter(p -> orgId.equals(p.getOrgId())).collect(Collectors.toList());
+		papersList.stream().forEach(paper -> {
+			paper.setStorage(storage);
+		});
+		if (papersList.size() > 0) {
+			paperRepo.saveAll(papersList);
 		}
-        PaperDetail paperDetail = Model.of(paperDetailRepo.findById(paperDetailId));
-        PaperDetailUnit paperDetailUnit = paperDetailUnitRepo.findTopByPaperDetailOrderByNumberDesc(paperDetail);
-
-        List<PaperDetailUnit> saveUnits = new ArrayList<>();
-        for (Question ques : questions) {
-            PaperDetailUnit pdu = new PaperDetailUnit(paper, paperDetail, ques);
-
-            //如果大题没有小题,取导入的试题分数
-            if (paperDetailUnit == null) {
-                pdu.setNumber(1);
-            } else {
-                pdu.setNumber(paperDetailUnit.getNumber());//设置为大题中最大的number
-            }
-
-            pdu.setScore(ques.getScore() == null ? 0d : ques.getScore());
-
-            //处理套题
-            if (pdu.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                List<Question> subQuestions = ques.getSubQuestions();
-                List<Double> subScoreList = new ArrayList<>();
-                if (subQuestions != null && subQuestions.size() > 0) {
-                    for (Question subQuestion : subQuestions) {
-                        subScoreList.add(CommonUtils.formatDouble(pdu.getScore() / subQuestions.size()));
-                    }
-                }
-                pdu.setSubScoreListNew(subScoreList);
-            }
-
-            saveUnits.add(pdu);
-        }
+	}
 
-        paperDetailUnitRepo.saveAll(saveUnits);
-
-        //清除缓存
-        this.clearPaperCache(paper.getId());
-        paper.setAuditStatus(false);
-        Double total = paper.getTotalScore();
-        Integer dc = paper.getPaperDetailCount();
-        Integer uc = paper.getUnitCount();
-        formatPaper(paper, user);
-
-        StringBuilder paperInfo = new StringBuilder();
-        paperInfo.append("课程:" + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")");
-        paperInfo.append(" 试卷名称:" + paper.getName());
-        paperInfo.append(" 第" + paperDetail.getNumber() + "大题选题题数:" + questions.size());
-        String changInfo = PaperUtil.getPaperChangeInfo(total, dc, uc, paper);
-        if (changInfo != null) {
-            paperInfo.append(changInfo);
-        }
-        ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(), AdminOperateType.TYPE49.getDesc(), paperInfo.toString()));
-        return paper;
-    }
-
-    public boolean checkPaperName(String paperName, String orgId) {
-        List<Paper> paperList = paperRepo.findByNameAndOrgId(paperName, orgId);
-        if (CollectionUtils.isNotEmpty(paperList)) {
-            return true;
-        }
-        return false;
-    }
+	/**
+	 * 批量不通过试卷
+	 *
+	 * @param paperIds
+	 */
+	public void noPassPapers(List<String> paperIds) {
+		List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
+		papers.stream().forEach(paper -> {
+			paper.setPaperStatus(PaperStatus.NOPASS);
+		});
+		paperRepo.saveAll(papers);
+	}
 
-    public void checkPaperNameNew(String paperName, String orgId) throws Exception {
-        List<Paper> paperList = paperRepo.findByNameAndOrgId(paperName, orgId);
-        if (paperList != null && paperList.size() > 0) {
-            throw new PaperException("试卷名称重复,请重新命名");
-        }
-    }
+	/**
+	 * 批量待审核试卷
+	 *
+	 * @param paperIds
+	 */
+	public void backPapers(List<String> paperIds) {
+		List<Paper> papers = CommonUtils.toList(paperRepo.findByIdIn(paperIds));
+		papers.stream().forEach(paper -> {
+			paper.setPaperStatus(PaperStatus.DRAFT);
+		});
+		paperRepo.saveAll(papers);
+	}
 
-    public Page<Paper> getPapersNotInIds(PaperSearchInfo paperSearchInfo, String[] ids, int curPage, int pageSize, PaperType paperType,UserDataRule ud) {
-        Set<String> selectedIds = new HashSet<>();
-        for (String id : ids) {
-            selectedIds.add(id);
-        }
-        if (ud.assertEmptyQueryResult()) {
-			return Page.empty();
+	/**
+	 * 初始化导出试卷DTO
+	 *
+	 * @param id
+	 * @return
+	 */
+	public PaperExp getPaperDto(String id) {
+		Paper paper = Model.of(paperRepo.findById(id));
+		// 创建paperDto
+		PaperExp paperExp = new PaperExp();
+		BeanUtils.copyProperties(paper, paperExp);
+		paperExp.setCourse(paper.getCourse());
+		// 获取大题
+		List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
+		List<PaperDetailExp> paperDetailExps = new ArrayList<>();
+		for (PaperDetail paperDetail : paperDetails) {
+			PaperDetailExp paperDetailExp = new PaperDetailExp();
+			BeanUtils.copyProperties(paperDetail, paperDetailExp);
+			paperDetailExps.add(paperDetailExp);
 		}
-        Query query = new Query();
-        query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
-        if (ud.assertNeedQueryRefIds()) {
-			query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
+		// 封装小题
+		for (int i = 0; i < paperDetailExps.size(); i++) {
+			List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo
+					.findByPaperDetailOrderByNumber(paperDetails.get(i));
+			if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
+				List<PaperDetailUnitExp> paperDetailUnitExps = new ArrayList<>();
+				for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+					PaperDetailUnitExp paperDetailUnitExp = new PaperDetailUnitExp();
+					BeanUtils.copyProperties(paperDetailUnit, paperDetailUnitExp);
+					fillProperty(paperDetailUnitExp.getQuestion());
+					if (!CollectionUtils.isEmpty(paperDetailUnitExp.getQuestion().getSubQuestions())) {
+						for (Question subque : paperDetailUnitExp.getQuestion().getSubQuestions()) {
+							fillProperty(subque);
+						}
+					}
+					paperDetailUnitExps.add(paperDetailUnitExp);
+				}
+				// 选择题,套题下选择题 选项顺序重新排列
+				reorderChoicequestionOption(paperDetailUnitExps);
+				paperDetailExps.get(i).setPaperDetailUnits(paperDetailUnitExps);
+			} else {
+				paperDetailExps.get(i).setUnitCount(0);
+			}
 		}
-        query.addCriteria(Criteria.where("paperType").is(paperType));
-        query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
-        query.addCriteria(Criteria.where("id").nin(selectedIds));
-        if(PaperType.GENERATE.equals(paperType)) {
-        	query.addCriteria(Criteria.where("storage").ne(1));
-        }
-        long total = this.mongoTemplate.count(query, Paper.class);
-        if (total == 0) {
-            return Page.empty();
-        }
+		paperExp.setPaperDetails(paperDetailExps);
+		// 初始化试卷内容
+		initPaper(paperExp);
+		return paperExp;
+	}
 
-        query.with(Sort.by(new Order(Direction.DESC, "createTime")));
-        query.limit(pageSize);
-        query.skip((curPage - 1L) * pageSize);
-        List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
-        Page<Paper> paperPageList = new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
-        return paperPageList;
-    }
-
-    /**
-     * 使用原卷
-     */
-    public void useBasePaper(String selectedPaperIds, String userId) {
-        Assert.hasLength(selectedPaperIds, "试卷id不能为空!");
-        String[] paperIds = selectedPaperIds.split(",");
-        for (int i = 0; i < paperIds.length; i++) {
-            Paper oldpaper = Model.of(paperRepo.findById(paperIds[i]));
-            List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(oldpaper);
-            for (PaperDetail paperDetail : paperDetails) {
-                List<PaperDetailUnit> paperDetailUnits = paperDetailUnitService.getUnitsByPaperDetail(paperDetail);
-                paperDetail.setPaperDetailUnits(paperDetailUnits);
-            }
-            //            PaperUtil.sortDetails(paperDetails);
-            oldpaper.setId(null);
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-            String createTime = sdf.format(new Date());
-            oldpaper.setCreateTime(createTime);
-            oldpaper.setCreator(userId);
-            oldpaper.setLastModifyName(userId);
-            oldpaper.setPaperType(PaperType.GENERATE);//试卷类型 修改为组卷
-            oldpaper.setAuditStatus(false);
-            Paper newPaper = paperRepo.save(oldpaper);
-            for (int j = 0; j < paperDetails.size(); j++) {
-                PaperDetail paperDetail = paperDetails.get(j);
-                paperDetail.setNumber(j + 1);
-                List<PaperDetailUnit> paperDetailUnits = paperDetail.getPaperDetailUnits();
-                paperDetail.setPaper(newPaper);//关联新Paper
-                paperDetail.setId(null);
-                paperDetail.setCreateTime(createTime);
-                paperDetail.setCreator(userId);
-                PaperDetail newPaperDetail = paperDetailRepo.save(paperDetail);//保存新的paperDetail
-                for (int k = 0; k < paperDetailUnits.size(); k++) {
-                    //重新设置保存PaperDetailUnit
-                    PaperDetailUnit paperDetailUnit = paperDetailUnits.get(k);
-                    paperDetailUnit.setPaper(newPaper);                //关联新Paper
-                    paperDetailUnit.setPaperDetail(newPaperDetail); //关联新paperDetail
-                    paperDetailUnit.setId(null);
-                    paperDetailUnit.setCreateTime(createTime);
-                    paperDetailUnit.setCreator(userId);
-                    paperDetailUnit.setPaperType(PaperType.GENERATE);
-                    paperDetailUnitRepo.save(paperDetailUnit);//保存新的paperDetailUnit
-                }
-            }
-        }
-    }
-
-    /**
-     * 根据试卷名称、试卷类型检查名称是否存在
-     *
-     * @param paperName
-     * @param paperType
-     * @param orgId
-     * @return
-     * @throws Exception
-     */
-    public boolean checkPaperName(String paperName, PaperType paperType, String orgId) throws Exception {
-        Query query = new Query();
-        query.addCriteria(Criteria.where("orgId").is(orgId));
-        query.addCriteria(Criteria.where("name").is(paperName.trim()));
-        query.addCriteria(Criteria.where("paperType").is(paperType));
-        List<Paper> papers = this.mongoTemplate.find(query, Paper.class);
-        if (papers != null && papers.size() > 0) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * 上传音频文件检查
-     *
-     * @param paperId
-     * @param filesName
-     * @return
-     */
-    public Map<String, String> checkRadioFile(String paperId, List<String> filesName) {
-        Map<String, String> messageMap = new HashMap<>();
-        //判断文件名中格式是否正确
-        for (String fileName : filesName) {
-            String[] fileNames = fileName.split("\\.");
-            String fileType = fileNames[fileNames.length - 1];
-            if (sysProperty.getRadioType().indexOf(fileType) < 0) {
-                messageMap.put("errorMsg", fileName + ",文件格式不正确,当前支持格式:" + sysProperty.getRadioType());
-                return messageMap;
-            }
-
-            String pattern_01 = "\\d+_1_\\d{1,2}";                //题干正则
-            String pattern_02 = "\\d+_2_[A-Z|a-z]_\\d{1,2}";    //选项正则
-            if (!(Pattern.matches(pattern_01, fileNames[0]) || Pattern.matches(pattern_02, fileNames[0]))) {
-                messageMap.put("errorMsg", fileName + "文件名格式不正确,请检查");
-                return messageMap;
-            }
-        }
+	/**
+	 * 重新对选择题option进行排序(多选、单选、套题下选择题)
+	 */
+	public void reorderChoicequestionOption(List<PaperDetailUnitExp> paperDetailUnitExps) {
+		for (PaperDetailUnitExp paperDetailUnitExp : paperDetailUnitExps) {
+			String optionOrder = paperDetailUnitExp.getOptionOrder();
+			if (StringUtil.isNotBlank(optionOrder)) {
+				Question question = paperDetailUnitExp.getQuestion();
+				if (question.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+						|| question.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+					question.setQuesOptions(reorderOptionCore(question.getQuesOptions(), optionOrder));
+				}
+				if (question.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+					List<Question> subQuestions = question.getSubQuestions();
+					int index = 0;
+					for (int k = 0; k < subQuestions.size(); k++) {
+						Question subQuestion = subQuestions.get(k);
+						if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+								|| subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+							subQuestion.setQuesOptions(
+									reorderOptionCore(subQuestion.getQuesOptions(), optionOrder.split(";")[index]));
+							index++;
+						}
+					}
+				}
+			}
+		}
+	}
 
-        //根据试卷id,查询该试卷
-        Paper paper = Model.of(paperRepo.findById(paperId));
+	private List<QuesOption> reorderOptionCore(List<QuesOption> quesOptions, String optionOrder) {
+		List<QuesOption> newQuesOptions = new ArrayList<>();
+		if (StringUtil.isBlank(optionOrder) || quesOptions.isEmpty()) {
+			return null;
+		}
+		String[] optionOrderArr = optionOrder.split(",");
+		for (int j = 0; j < optionOrderArr.length; j++) {
+			for (int k = 0; k < quesOptions.size(); k++) {
+				if (optionOrderArr[j].equals(quesOptions.get(k).getNumber())) {
+					newQuesOptions.add(quesOptions.get(k));
+				}
+			}
+		}
+		quesOptions = null;
+		return newQuesOptions;
+	}
 
-        //根据试卷查询所有的小题
-        List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+	/**
+	 * 初始化试卷内容(增加序号)
+	 *
+	 * @param paperExp
+	 */
+	public void initPaper(PaperExp paperExp) {
+		if (paperExp.getPaperDetails() == null || paperExp.getPaperDetails().size() == 0) {
+			return;
+		}
+		int mainNum = 0;
+		List<PaperDetailExp> paperDetailExpList = paperExp.getPaperDetails();
+		for (PaperDetailExp paperDetail : paperDetailExpList) {
+			// 大题序号
+			paperDetail.setNumber(++mainNum);
+			paperDetail.setCnNum(CommonUtils.toCHNum(paperDetail.getNumber()));
+			if (paperDetail != null && paperDetail.getPaperDetailUnits() != null
+					&& paperDetail.getPaperDetailUnits().size() > 0) {
+				for (PaperDetailUnitExp paperDetailUnit : paperDetail.getPaperDetailUnits()) {
+					Question question = paperDetailUnit.getQuestion();
+					if (question != null) {
+						if (question.getHasAudio() != null && question.getHasAudio()) {
+							paperExp.setHasAudio(true); // 设置试卷含有音频
+						}
+						quesService.formatQuesUnit(question);
+						List<Question> subQuesList = question.getSubQuestions();
+						// 套题序号
+						if (subQuesList != null && subQuesList.size() > 0) {
+							int index = 0;
+							for (Question subQues : subQuesList) {
+								Map<String, String> params = new HashMap<>();
+								params.put("number", String.valueOf(++index));
+								subQues.setQuesParams(params);
+								quesService.formatQuesUnit(subQues);
+							}
+							String quesBodyHtml = CommonUtils.relaceQuestionIdx(question.getQuesBody(), 0);
+							question.setQuesBody(quesBodyHtml);
+						}
+					}
+				}
+			}
+		}
+	}
 
-        String names = "";
-        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-            names = names + paperDetailUnit.getNumber().toString() + ",";
-        }
+	/**
+	 * 格式化查询条件
+	 *
+	 * @param paperSearchInfo
+	 */
+	public void formatPaperSearchInfo(PaperSearchInfo paperSearchInfo) {
+		if (StringUtils.isEmpty(paperSearchInfo.getCourseNo())) {
+			paperSearchInfo.setCourseNo(null);
+		}
+		if (StringUtils.isEmpty(paperSearchInfo.getCreateTime())) {
+			paperSearchInfo.setCreateTime(null);
+		}
+		if (StringUtils.isEmpty(paperSearchInfo.getCreator())) {
+			paperSearchInfo.setCreator(null);
+		}
+		if (StringUtils.isEmpty(paperSearchInfo.getName())) {
+			paperSearchInfo.setName(null);
+		}
+	}
 
-        for (String fileName : filesName) {
-            String fileNames[] = fileName.split("_");
-
-            //先判断小题号是否正确
-            String fileNameFirst = fileNames[0];
-            if (!names.contains(fileNameFirst)) {
-                messageMap.put("errorMsg", fileName + "文件,试卷中没有对应的小题");
-                return messageMap;
-            }
-
-            //再判断题干中是否存在ABCD
-            String fileNameSecond = fileNames[1];
-            if (fileNameSecond.equals("1")) {
-                if (fileNames.length > 3) {
-                    Matcher m = Pattern.compile(".*[a-zA-Z]+.*").matcher(fileNames[2]);
-                    if (m.matches()) {
-                        messageMap.put("errorMsg", fileName + "文件名称不对,文件名为题干,但存在选项");
-                        return messageMap;
-                    }
-                }
-            }
-
-            //判断选项
-            else if (fileNameSecond.equals("2")) {
-                messageMap = checkOptions(paperDetailUnits, fileNames, fileName);
-                if (messageMap != null) {
-                    return messageMap;
-                }
-                messageMap = new HashMap<>();
-            } else {
-                messageMap.put("errorMsg", fileName + "文件名称不对,无法识别为题干或选项 ");
-                return messageMap;
-            }
-        }
+	/**
+	 * 1.重新设置小题number,并返回试卷总分 2.重新计算大题的总分和小题总数 3.重新保存试卷大题数量、小题数量、试卷总分等信息
+	 *
+	 * @param paper
+	 */
+	public void formatPaper(Paper paper, User user) {
+		double paperTotalScore = reSetPaperDetailUnit(paper);
+		paper.setDifficultyDegree(reSetDifficulty(paper, paperTotalScore));
+		Map<String, Object> paperInfoMap = reSetPaperDetail(paper);
+		reSetPaper(paper, user, paperInfoMap, paperTotalScore);
+	}
 
-        messageMap.put("errorMsg", "OK");
-        return messageMap;
-    }
-
-    //判断选项
-    public Map<String, String> checkOptions(List<PaperDetailUnit> paperDetailUnits, String fileNames[], String fileName) {
-        Map<String, String> messageMap = new HashMap<>();
-        for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-            if (paperDetailUnit.getNumber().toString().equals(fileNames[0])) {
-                //判断是否为选择题
-                if (paperDetailUnit.getQuestionType() != QuesStructType.SINGLE_ANSWER_QUESTION
-                        && paperDetailUnit.getQuestionType() != QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                    Matcher m = Pattern.compile(".*[a-zA-Z]+.*").matcher(fileNames[2]);
-                    if (m.matches()) {
-                        messageMap.put("errorMsg", fileName + "文件名称有误,题目不是选择题");
-                        return messageMap;
-                    }
-                    break;
-                } else {
-                    List<QuesOption> options = paperDetailUnit.getQuestion().getQuesOptions();
-                    String option = "";
-                    for (QuesOption quesOption : options) {
-                        option = option + quesOption.getNumber() + ",";
-                    }
-                    Integer integer = CommonUtils.characterToNumber(fileNames[2]);
-                    if (!option.contains(integer.toString())) {
-                        messageMap.put("errorMsg", fileName + "文件名称有误,题目中没有对应的" + fileNames[2] + "选项");
-                        return messageMap;
-                    }
-                    break;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * 上传音频文件到又拍云
-     *
-     * @param files
-     * @param paperId
-     * @throws IOException
-     */
-    public void uploadRadio(List<MultipartFile> files, String paperId, User user) {
-        //根据试卷id,查询该试卷
-        Paper paper = Model.of(paperRepo.findById(paperId));
-
-        for (MultipartFile file : files) {
-            //判断文件大小
-            long fileSize = file.getSize();
-            int size = Integer.parseInt(sysProperty.getAudioMaxsize());
-            if (fileSize > (size * 1048576L)) {
-                throw new StatusException("400", "音频文件过大,限制" + size + "M内!");
-            }
-
-            //根据试卷查询所有的小题,根据文件名匹配出当前小题ID
-            String numbers[] = file.getOriginalFilename().split("_");
-            if (numbers.length < 3) {
-                throw new StatusException("400", "音频文件命名格式错误!");
-            }
-
-            PaperDetailUnit unit = paperDetailUnitRepo.findByPaperAndNumber(paper, Integer.valueOf(numbers[0]));
-            if (unit == null || unit.getQuestion() == null) {
-                throw new StatusException("400", "对应试题不存在!");
-            }
-
-            Question question = unit.getQuestion();
-            uploadAudioFile(paperId, question.getId(), file, user);
-            appendAudioTag(file.getOriginalFilename(), question.getId());
-
-            //清除缓存
-            this.clearQuestionCache(question.getId());
-        }
+	/**
+	 * 计算试卷难度
+	 *
+	 * @param paper
+	 * @param paperTotalScore
+	 * @return
+	 */
+
+	@SuppressWarnings("unused")
+	public Double reSetDifficulty(Paper paper, double paperTotalScore) {
+		Double sum = 0.0;
+		List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
+		if (paperDetails != null && paperDetails.size() > 0) {
+			for (PaperDetail paperDetail : paperDetails) {
+				// 获取每个大题下面的所有小题
+				List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo
+						.findByPaperDetailOrderByNumber(paperDetail);
+				if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
+					for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+						// 如果为套题,重新计算难度
+						if (paperDetailUnit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+							// 重新计算套题的难度,公开度
+							boolean publicity = false;
+							double totalSum = 0d;
+							double totalDou = 0d;
+							for (int i = 0; i < paperDetailUnit.getQuestion().getSubQuestions().size(); i++) {
+								Question subQuestion = paperDetailUnit.getQuestion().getSubQuestions().get(i);
+								// 设置公开度
+								if (subQuestion.getPublicity() == null) {
+									publicity = true;
+								} else {
+									if (subQuestion.getPublicity()) {
+										publicity = true;
+									}
+								}
+								if (subQuestion.getDifficultyDegree() == null) {
+									subQuestion.setDifficultyDegree(0.5);
+								}
+								totalSum = subQuestion.getDifficultyDegree() * paperDetailUnit.getSubScoreList().get(i)
+										+ totalSum;
+								totalDou = paperDetailUnit.getSubScoreList().get(i) + totalDou;
+							}
+
+							BigDecimal b;
+							if (totalDou != 0d) {
+								b = BigDecimal.valueOf(totalSum / totalDou);
+							} else {
+								b = BigDecimal.valueOf(0d);
+							}
+
+							Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+							paperDetailUnit.getQuestion().setDifficultyDegree(difficulty);
+							paperDetailUnit.setScore(totalDou);
+						}
+						// 旧题没有难度值,需要重新赋值
+						if (paperDetailUnit.getQuestion().getDifficultyDegree() == null) {
+							paperDetailUnit.getQuestion().setDifficultyDegree(0.5);
+						}
+						if (paperDetailUnit.getScore() == null) {
+							paperDetailUnit.setScore(0d);
+						}
+						sum = paperDetailUnit.getScore() * paperDetailUnit.getQuestion().getDifficultyDegree() + sum;
+					}
+				}
+			}
+			if (paperTotalScore < 0.1) {
+				return 0.0;
+			}
+			BigDecimal b = BigDecimal.valueOf(sum / paperTotalScore);
+			Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+			return difficulty;
+		}
+		return 0.0;
+	}
 
-        //删除服务器文件夹
-        String mp3DirectoryPath = TEMP_FILE_EXP + File.separator + paperId;
-        try {
-            File mp3Directory = new File(mp3DirectoryPath);
-            FileUtils.deleteDirectory(mp3Directory);
-        } catch (IOException e) {
-            LOG.error(e.getMessage(), e);
-        }
-    }
-
-    /**
-     * 上传音频文件至又拍云
-     */
-    private void uploadAudioFile(String paperId, String questionId, MultipartFile file, User user) {
-        //文件夹不存在则新建
-        final String mp3DirectoryPath = TEMP_FILE_EXP + File.separator + paperId;
-        File mp3Directory = new File(mp3DirectoryPath);
-        if (!mp3Directory.exists()) {
-            mp3Directory.mkdirs();
-        }
+	/**
+	 * 重新设置小题number,并返回试卷总分
+	 *
+	 * @param paper
+	 * @return
+	 */
+	private double reSetPaperDetailUnit(Paper paper) {
+		List<PaperDetailUnit> paperDetailUnitAll = paperDetailUnitService.findByPaperAndSort(paper);
+		double totalScore = 0;
+		for (int i = 0; i < paperDetailUnitAll.size(); i++) {
+			PaperDetailUnit unit = paperDetailUnitAll.get(i);
+			if (unit.getScore() != null) {
+				totalScore += unit.getScore();
+			}
+			unit.setNumber(i + 1);
+		}
+		paperDetailUnitRepo.saveAll(paperDetailUnitAll);
+		totalScore = CommonUtils.formatDouble(totalScore);
+		return totalScore;
+	}
 
-        //文件名包含随机数,防止缓存
-        int randomNumber = random.nextInt(1000);
-        final String mp3FileName = questionId + "_" + randomNumber + "_" + file.getOriginalFilename();
-        File mp3File = new File(mp3DirectoryPath + File.separator + mp3FileName);
-
-        FileOutputStream outputStream;
-        try {
-            outputStream = new FileOutputStream(mp3File);
-        } catch (FileNotFoundException e) {
-            LOG.error(e.getMessage(), e);
-            throw new StatusException("500", "音频文件处理失败!");
-        }
+	/**
+	 * 重新计算大题的总分和小题总数
+	 *
+	 * @param paper
+	 */
+	private Map<String, Object> reSetPaperDetail(Paper paper) {
+		Map<String, Object> paperInfoMap = new HashMap<>();
+		int allQuesCount = 0;
+		List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
+		// 计算各大题总分和小题数量
+		for (PaperDetail paperDetail : paperDetails) {
+			List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperDetailOrderByNumber(paperDetail);
+			if (paperDetailUnits.size() > 0) {
+				int count = 0;
+				double score = 0;
+				int nestQusNum = 0;
+				for (PaperDetailUnit unit : paperDetailUnits) {
+					if (unit.getScore() != null) {
+						score += unit.getScore();
+					}
+					if (unit.getQuestion() != null && unit.getQuestion().getSubQuestions() != null
+							&& unit.getQuestion().getSubQuestions().size() > 0) {
+						nestQusNum += unit.getQuestion().getSubQuestions().size() - 1;
+					}
+				}
+				count = paperDetailUnits.size() + nestQusNum;
+				score = CommonUtils.formatDouble(score);
+				paperDetail.setScore(score);
+				paperDetail.setUnitCount(count);
+				allQuesCount += count;
+			} else {
+				paperDetail.setScore(0d);
+			}
+		}
+		paperDetailRepo.saveAll(paperDetails);
+		paperInfoMap.put("allQuesCount", allQuesCount);
+		paperInfoMap.put("paperDetails", paperDetails);
+		return paperInfoMap;
+	}
 
-        try (InputStream is = file.getInputStream();
-             BufferedInputStream bis = new BufferedInputStream(is, 1024 * 10);) {
-            int read;
-            byte[] bytes = new byte[1024 * 4];
-            while ((read = bis.read(bytes, 0, 1024 * 4)) != -1) {
-                outputStream.write(bytes, 0, read);
-            }
-
-            //上传到又拍云
-            //            UpYun upYun = new UpYun(sysProperty.getBucketName(), sysProperty.getUserName(), sysProperty.getPassword());
-            //            upYun.writeFile(sysProperty.getRadioUploadPath() + mp3FileName, mp3File, true);
-            //通用存储
-            FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
-            env.setRelativePath(sysProperty.getRadioUploadPath() + mp3FileName);
-            YunPathInfo pi = FileStorageUtil.saveFile("audioFile", env, mp3File, null);
-            IoUtils.removeFile(mp3File);
-
-            //保存记录
-            QuestionAudio audio = new QuestionAudio(questionId, file.getOriginalFilename(), pi.getRelativePath());
-            questionAudioService.saveQuestionAudio(audio, user);
-        } catch (Exception e) {
-            LOG.error(e.getMessage(), e);
-            throw new StatusException("500", "音频文件保存失败!");
-        } finally {
-            try {
-                if (outputStream != null) {
-                    outputStream.close();
-                }
-            } catch (IOException e) {
-                //ignore
-            }
-        }
-    }
-
-    /**
-     * 音频文件插入到标签
-     */
-    private void appendAudioTag(String fileName, String questionId) {
-        QuestionAudio questionAudio = questionAudioService.findByQuestionIdAndFileName(questionId, fileName);
-        if (questionAudio == null) {
-            return;
-        }
+	/**
+	 * 重新设置试卷大题数量、小题数量、试卷总分等属性
+	 *
+	 * @param paper
+	 * @param user
+	 * @param paperInfoMap
+	 * @param paperTotalScore
+	 */
+	@SuppressWarnings("unchecked")
+	private void reSetPaper(Paper paper, User user, Map<String, Object> paperInfoMap, double paperTotalScore) {
+		List<PaperDetail> paperDetails = (List<PaperDetail>) paperInfoMap.get("paperDetails");
+		paper.setPaperDetailCount(paperDetails.size());// 设置大题数量
+		paper.setUnitCount(Integer.parseInt(paperInfoMap.get("allQuesCount") + ""));// 设置小题数量
+		paper.setTotalScore(paperTotalScore);// 设置试卷总分
+		if (user != null) {
+			paper.setLastModifyName(user.getDisplayName());
+		}
+		paperRepo.save(paper);
 
-        Question question = Model.of(quesRepo.findById(questionAudio.getQuestionId()));
+		// 清除缓存
+		this.clearPaperCache(paper.getId());
+	}
+
+	/**
+	 * 先备份准备删掉的试题,然后再删掉
+	 *
+	 * @param questionId
+	 * @return
+	 */
+	public void deleteImportQuestionById(String detailUnitId, String questionId, User user) {
+		if(randomPaperQuestionService.existQuestion(questionId)) {
+        	throw new StatusException("该试题已被抽题模板使用,不能删除");
+        }
+		PaperDetailUnit paperDetailUnit = null;
+		if (detailUnitId != null) {
+			paperDetailUnit = Model.of(paperDetailUnitRepo.findById(detailUnitId));
+			Paper paper = paperDetailUnit.getPaper();
+			if (paper.getInUse() != null && paper.getInUse() == 1) {
+				throw new StatusException("500", "试卷已调用");
+			}
+		}
+		Question ques = Model.of(quesRepo.findById(questionId));
+
+		List<PaperDetailUnit> pdus = CommonUtils.toList(paperDetailUnitRepo.findByQuestion(ques));
+		List<String> paperNames = new ArrayList<>();
+
+		// 需要删除的小题
+		List<PaperDetailUnit> needPdus = new ArrayList<>();
+
+		List<Paper> papers = new ArrayList<>();
+		for (PaperDetailUnit pdu : pdus) {
+			if (pdu.getPaper() != null) {
+				if (!papers.contains(pdu.getPaper())) {
+					papers.add(pdu.getPaper());
+				}
+				if (PaperType.GENERATE == pdu.getPaper().getPaperType()) {
+					paperNames.add(pdu.getPaper().getName());
+				}
+			}
+		}
+		String changInfo = null;
+		if (paperNames.size() == 0) {
+			needPdus.addAll(pdus);// 此试题没有被组卷调用,则可以删除此试题
+			paperDetailUnitRepo.deleteAll(needPdus);
+			quesBakRepo.save(BeanCopierUtil.copyProperties(ques, QuestionBak.class));
+			quesRepo.delete(ques);
+
+			for (Paper paper : papers) {
+				if (paperDetailUnit != null && paperDetailUnit.getPaper().getId().equals(paper.getId())) {
+					Double total = paper.getTotalScore();
+					Integer dc = paper.getPaperDetailCount();
+					Integer uc = paper.getUnitCount();
+					formatPaper(paper, user);
+					changInfo = PaperUtil.getPaperChangeInfo(total, dc, uc, paper);
+				} else {
+					formatPaper(paper, user);
+				}
+			}
+		}
+
+		if (paperDetailUnit != null) {
+			StringBuilder sb = new StringBuilder();
+			sb.append("课程:" + paperDetailUnit.getPaper().getCourse().getName() + "("
+					+ paperDetailUnit.getPaper().getCourse().getCode() + ")");
+			sb.append(" 试卷名称:" + paperDetailUnit.getPaper().getName());
+			sb.append(" 第" + paperDetailUnit.getNumber() + "小题 ");
+			sb.append(changInfo);
+			ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+					AdminOperateType.TYPE48.getDesc(), sb.toString()));
+		}
+		if (CollectionUtils.isNotEmpty(paperNames)) {
+			throw new StatusException("该试题被试卷:" + StringUtils.join(paperNames, ",") + "使用,不能删除");
+		}
+	}
+
+	/**
+	 * 向试卷中插入一个试题
+	 *
+	 * @param paperId
+	 * @param paperDetailId
+	 * @return
+	 */
+	public Paper insertQuestionToPaper(String paperId, String paperDetailId, Question question, User user) {
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		question.setOrgId(user.getRootOrgId().toString());
+		question.setCourse(paper.getCourse());
+		question.setCourseNo(paper.getCourse().getCode());// 必须设置,因为部分方法需要courseNo作为查询条件
+		question = quesService.saveQues(question);
+
+		PaperDetail paperDetail = Model.of(paperDetailRepo.findById(paperDetailId));
+		PaperDetailUnit paperDetailUnit = new PaperDetailUnit(paper, paperDetail, question);
+		paperDetailUnit.setPaperType(PaperType.IMPORT);
+		PaperDetailUnit paperDetailUnit2 = paperDetailUnitService.findTopOrderByNumber(paperDetail, "DESC");
+		if (paperDetailUnit2 == null) {
+			paperDetailUnit.setNumber(1);
+		} else {
+			paperDetailUnit.setNumber(paperDetailUnit2.getNumber());// 设置number为大题的最大题号
+		}
+		paperDetailUnitRepo.save(paperDetailUnit);
+		formatPaper(paper, user);
+		return paper;
+	}
+
+	/**
+	 * 获取试题所在的试卷名称
+	 *
+	 * @param questionId
+	 * @return
+	 */
+	public List<String> getPaperNamesByQuestionId(String questionId) {
+		List<String> paperNames = new ArrayList<>();
+		List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByQuestion(Model.of(quesRepo.findById(questionId)));
+		for (PaperDetailUnit pdu : pdus) {
+			paperNames.add(pdu.getPaper().getName());
+		}
+		return paperNames;
+
+	}
+
+	public Page<Question> listQuestionforSelect(String paperId, int curPage, int pageSize, QuesStructType quesType,
+			User user, String quesBody, UserDataRule ud) {
+		Set<String> selectedIds = new HashSet<>();
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		List<PaperDetailUnit> pdus = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+		for (PaperDetailUnit pdu : pdus) {
+			selectedIds.add(pdu.getQuestion().getId());
+		}
+		return quesService.findByIdExclude(selectedIds, paper.getCourseNo(), quesType, curPage, pageSize,
+				user.getRootOrgId(), quesBody, ud);
+	}
+
+	@SuppressWarnings("unused")
+	public Paper selectQuestionsToPaper(String paperId, String paperDetailId, List<Question> questions, User user) {
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		if (paper.getInUse() != null && paper.getInUse() == 1) {
+			throw new StatusException("500", "试卷已调用");
+		}
+		PaperDetail paperDetail = Model.of(paperDetailRepo.findById(paperDetailId));
+		PaperDetailUnit paperDetailUnit = paperDetailUnitRepo.findTopByPaperDetailOrderByNumberDesc(paperDetail);
+
+		List<PaperDetailUnit> saveUnits = new ArrayList<>();
+		for (Question ques : questions) {
+			PaperDetailUnit pdu = new PaperDetailUnit(paper, paperDetail, ques);
+
+			// 如果大题没有小题,取导入的试题分数
+			if (paperDetailUnit == null) {
+				pdu.setNumber(1);
+			} else {
+				pdu.setNumber(paperDetailUnit.getNumber());// 设置为大题中最大的number
+			}
+
+			pdu.setScore(ques.getScore() == null ? 0d : ques.getScore());
+
+			// 处理套题
+			if (pdu.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+				List<Question> subQuestions = ques.getSubQuestions();
+				List<Double> subScoreList = new ArrayList<>();
+				if (subQuestions != null && subQuestions.size() > 0) {
+					for (Question subQuestion : subQuestions) {
+						subScoreList.add(CommonUtils.formatDouble(pdu.getScore() / subQuestions.size()));
+					}
+				}
+				pdu.setSubScoreListNew(subScoreList);
+			}
+
+			saveUnits.add(pdu);
+		}
+
+		paperDetailUnitRepo.saveAll(saveUnits);
+
+		// 清除缓存
+		this.clearPaperCache(paper.getId());
+		paper.setAuditStatus(false);
+		Double total = paper.getTotalScore();
+		Integer dc = paper.getPaperDetailCount();
+		Integer uc = paper.getUnitCount();
+		formatPaper(paper, user);
+
+		StringBuilder paperInfo = new StringBuilder();
+		paperInfo.append("课程:" + paper.getCourse().getName() + "(" + paper.getCourse().getCode() + ")");
+		paperInfo.append(" 试卷名称:" + paper.getName());
+		paperInfo.append(" 第" + paperDetail.getNumber() + "大题选题题数:" + questions.size());
+		String changInfo = PaperUtil.getPaperChangeInfo(total, dc, uc, paper);
+		if (changInfo != null) {
+			paperInfo.append(changInfo);
+		}
+		ReportsUtil.report(new AdminOperateReport(user.getRootOrgId(), user.getUserId(),
+				AdminOperateType.TYPE49.getDesc(), paperInfo.toString()));
+		return paper;
+	}
+
+	public boolean checkPaperName(String paperName, String orgId) {
+		List<Paper> paperList = paperRepo.findByNameAndOrgId(paperName, orgId);
+		if (CollectionUtils.isNotEmpty(paperList)) {
+			return true;
+		}
+		return false;
+	}
+
+	public void checkPaperNameNew(String paperName, String orgId) throws Exception {
+		List<Paper> paperList = paperRepo.findByNameAndOrgId(paperName, orgId);
+		if (paperList != null && paperList.size() > 0) {
+			throw new PaperException("试卷名称重复,请重新命名");
+		}
+	}
+
+	public Page<Paper> getPapersNotInIds(PaperSearchInfo paperSearchInfo, String[] ids, int curPage, int pageSize,
+			PaperType paperType, UserDataRule ud) {
+		Set<String> selectedIds = new HashSet<>();
+		for (String id : ids) {
+			selectedIds.add(id);
+		}
+		if (ud.assertEmptyQueryResult()) {
+			return Page.empty();
+		}
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(paperSearchInfo.getOrgId()));
+		if (ud.assertNeedQueryRefIds()) {
+			query.addCriteria(Criteria.where("course.id").in(ud.stringRefIds()));
+		}
+		query.addCriteria(Criteria.where("paperType").is(paperType));
+		query.addCriteria(Criteria.where("course.code").is(paperSearchInfo.getCourseNo()));
+		query.addCriteria(Criteria.where("id").nin(selectedIds));
+		if (PaperType.GENERATE.equals(paperType)) {
+			query.addCriteria(Criteria.where("storage").ne(1));
+		}
+		long total = this.mongoTemplate.count(query, Paper.class);
+		if (total == 0) {
+			return Page.empty();
+		}
+
+		query.with(Sort.by(new Order(Direction.DESC, "createTime")));
+		query.limit(pageSize);
+		query.skip((curPage - 1L) * pageSize);
+		List<Paper> paperList = this.mongoTemplate.find(query, Paper.class);
+		Page<Paper> paperPageList = new PageImpl<Paper>(paperList, PageRequest.of(curPage - 1, pageSize), total);
+		return paperPageList;
+	}
+
+	/**
+	 * 使用原卷
+	 */
+	public void useBasePaper(String selectedPaperIds, String userId) {
+		Assert.hasLength(selectedPaperIds, "试卷id不能为空!");
+		String[] paperIds = selectedPaperIds.split(",");
+		for (int i = 0; i < paperIds.length; i++) {
+			Paper oldpaper = Model.of(paperRepo.findById(paperIds[i]));
+			List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(oldpaper);
+			for (PaperDetail paperDetail : paperDetails) {
+				List<PaperDetailUnit> paperDetailUnits = paperDetailUnitService.getUnitsByPaperDetail(paperDetail);
+				paperDetail.setPaperDetailUnits(paperDetailUnits);
+			}
+			// PaperUtil.sortDetails(paperDetails);
+			oldpaper.setId(null);
+			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+			String createTime = sdf.format(new Date());
+			oldpaper.setCreateTime(createTime);
+			oldpaper.setCreator(userId);
+			oldpaper.setLastModifyName(userId);
+			oldpaper.setPaperType(PaperType.GENERATE);// 试卷类型 修改为组卷
+			oldpaper.setAuditStatus(false);
+			Paper newPaper = paperRepo.save(oldpaper);
+			for (int j = 0; j < paperDetails.size(); j++) {
+				PaperDetail paperDetail = paperDetails.get(j);
+				paperDetail.setNumber(j + 1);
+				List<PaperDetailUnit> paperDetailUnits = paperDetail.getPaperDetailUnits();
+				paperDetail.setPaper(newPaper);// 关联新Paper
+				paperDetail.setId(null);
+				paperDetail.setCreateTime(createTime);
+				paperDetail.setCreator(userId);
+				PaperDetail newPaperDetail = paperDetailRepo.save(paperDetail);// 保存新的paperDetail
+				for (int k = 0; k < paperDetailUnits.size(); k++) {
+					// 重新设置保存PaperDetailUnit
+					PaperDetailUnit paperDetailUnit = paperDetailUnits.get(k);
+					paperDetailUnit.setPaper(newPaper); // 关联新Paper
+					paperDetailUnit.setPaperDetail(newPaperDetail); // 关联新paperDetail
+					paperDetailUnit.setId(null);
+					paperDetailUnit.setCreateTime(createTime);
+					paperDetailUnit.setCreator(userId);
+					paperDetailUnit.setPaperType(PaperType.GENERATE);
+					paperDetailUnitRepo.save(paperDetailUnit);// 保存新的paperDetailUnit
+				}
+			}
+		}
+	}
+
+	/**
+	 * 根据试卷名称、试卷类型检查名称是否存在
+	 *
+	 * @param paperName
+	 * @param paperType
+	 * @param orgId
+	 * @return
+	 * @throws Exception
+	 */
+	public boolean checkPaperName(String paperName, PaperType paperType, String orgId) throws Exception {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("orgId").is(orgId));
+		query.addCriteria(Criteria.where("name").is(paperName.trim()));
+		query.addCriteria(Criteria.where("paperType").is(paperType));
+		List<Paper> papers = this.mongoTemplate.find(query, Paper.class);
+		if (papers != null && papers.size() > 0) {
+			return false;
+		}
+		return true;
+	}
+
+	/**
+	 * 上传音频文件检查
+	 *
+	 * @param paperId
+	 * @param filesName
+	 * @return
+	 */
+	public Map<String, String> checkRadioFile(String paperId, List<String> filesName) {
+		Map<String, String> messageMap = new HashMap<>();
+		// 判断文件名中格式是否正确
+		for (String fileName : filesName) {
+			String[] fileNames = fileName.split("\\.");
+			String fileType = fileNames[fileNames.length - 1];
+			if (sysProperty.getRadioType().indexOf(fileType) < 0) {
+				messageMap.put("errorMsg", fileName + ",文件格式不正确,当前支持格式:" + sysProperty.getRadioType());
+				return messageMap;
+			}
+
+			String pattern_01 = "\\d+_1_\\d{1,2}"; // 题干正则
+			String pattern_02 = "\\d+_2_[A-Z|a-z]_\\d{1,2}"; // 选项正则
+			if (!(Pattern.matches(pattern_01, fileNames[0]) || Pattern.matches(pattern_02, fileNames[0]))) {
+				messageMap.put("errorMsg", fileName + "文件名格式不正确,请检查");
+				return messageMap;
+			}
+		}
 
-        //正则匹配音频标签
-        Pattern audioPattern = Pattern.compile(String.format("<a id=\"[\\d,\\w]+\" name=\"%s\"></a>", fileName));
+		// 根据试卷id,查询该试卷
+		Paper paper = Model.of(paperRepo.findById(paperId));
+
+		// 根据试卷查询所有的小题
+		List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+
+		String names = "";
+		for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+			names = names + paperDetailUnit.getNumber().toString() + ",";
+		}
+
+		for (String fileName : filesName) {
+			String fileNames[] = fileName.split("_");
+
+			// 先判断小题号是否正确
+			String fileNameFirst = fileNames[0];
+			if (!names.contains(fileNameFirst)) {
+				messageMap.put("errorMsg", fileName + "文件,试卷中没有对应的小题");
+				return messageMap;
+			}
+
+			// 再判断题干中是否存在ABCD
+			String fileNameSecond = fileNames[1];
+			if (fileNameSecond.equals("1")) {
+				if (fileNames.length > 3) {
+					Matcher m = Pattern.compile(".*[a-zA-Z]+.*").matcher(fileNames[2]);
+					if (m.matches()) {
+						messageMap.put("errorMsg", fileName + "文件名称不对,文件名为题干,但存在选项");
+						return messageMap;
+					}
+				}
+			}
+
+			// 判断选项
+			else if (fileNameSecond.equals("2")) {
+				messageMap = checkOptions(paperDetailUnits, fileNames, fileName);
+				if (messageMap != null) {
+					return messageMap;
+				}
+				messageMap = new HashMap<>();
+			} else {
+				messageMap.put("errorMsg", fileName + "文件名称不对,无法识别为题干或选项 ");
+				return messageMap;
+			}
+		}
+
+		messageMap.put("errorMsg", "OK");
+		return messageMap;
+	}
+
+	// 判断选项
+	public Map<String, String> checkOptions(List<PaperDetailUnit> paperDetailUnits, String fileNames[],
+			String fileName) {
+		Map<String, String> messageMap = new HashMap<>();
+		for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+			if (paperDetailUnit.getNumber().toString().equals(fileNames[0])) {
+				// 判断是否为选择题
+				if (paperDetailUnit.getQuestionType() != QuesStructType.SINGLE_ANSWER_QUESTION
+						&& paperDetailUnit.getQuestionType() != QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+					Matcher m = Pattern.compile(".*[a-zA-Z]+.*").matcher(fileNames[2]);
+					if (m.matches()) {
+						messageMap.put("errorMsg", fileName + "文件名称有误,题目不是选择题");
+						return messageMap;
+					}
+					break;
+				} else {
+					List<QuesOption> options = paperDetailUnit.getQuestion().getQuesOptions();
+					String option = "";
+					for (QuesOption quesOption : options) {
+						option = option + quesOption.getNumber() + ",";
+					}
+					Integer integer = CommonUtils.characterToNumber(fileNames[2]);
+					if (!option.contains(integer.toString())) {
+						messageMap.put("errorMsg", fileName + "文件名称有误,题目中没有对应的" + fileNames[2] + "选项");
+						return messageMap;
+					}
+					break;
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * 上传音频文件到又拍云
+	 *
+	 * @param files
+	 * @param paperId
+	 * @throws IOException
+	 */
+	public void uploadRadio(List<MultipartFile> files, String paperId, User user) {
+		// 根据试卷id,查询该试卷
+		Paper paper = Model.of(paperRepo.findById(paperId));
+
+		for (MultipartFile file : files) {
+			// 判断文件大小
+			long fileSize = file.getSize();
+			int size = Integer.parseInt(sysProperty.getAudioMaxsize());
+			if (fileSize > (size * 1048576L)) {
+				throw new StatusException("400", "音频文件过大,限制" + size + "M内!");
+			}
+
+			// 根据试卷查询所有的小题,根据文件名匹配出当前小题ID
+			String numbers[] = file.getOriginalFilename().split("_");
+			if (numbers.length < 3) {
+				throw new StatusException("400", "音频文件命名格式错误!");
+			}
+
+			PaperDetailUnit unit = paperDetailUnitRepo.findByPaperAndNumber(paper, Integer.valueOf(numbers[0]));
+			if (unit == null || unit.getQuestion() == null) {
+				throw new StatusException("400", "对应试题不存在!");
+			}
+
+			Question question = unit.getQuestion();
+			uploadAudioFile(paperId, question.getId(), file, user);
+			appendAudioTag(file.getOriginalFilename(), question.getId());
+
+			// 清除缓存
+			this.clearQuestionCache(question.getId());
+		}
+
+		// 删除服务器文件夹
+		String mp3DirectoryPath = TEMP_FILE_EXP + File.separator + paperId;
+		try {
+			File mp3Directory = new File(mp3DirectoryPath);
+			FileUtils.deleteDirectory(mp3Directory);
+		} catch (IOException e) {
+			LOG.error(e.getMessage(), e);
+		}
+	}
+
+	/**
+	 * 上传音频文件至又拍云
+	 */
+	private void uploadAudioFile(String paperId, String questionId, MultipartFile file, User user) {
+		// 文件夹不存在则新建
+		final String mp3DirectoryPath = TEMP_FILE_EXP + File.separator + paperId;
+		File mp3Directory = new File(mp3DirectoryPath);
+		if (!mp3Directory.exists()) {
+			mp3Directory.mkdirs();
+		}
+
+		// 文件名包含随机数,防止缓存
+		int randomNumber = random.nextInt(1000);
+		final String mp3FileName = questionId + "_" + randomNumber + "_" + file.getOriginalFilename();
+		File mp3File = new File(mp3DirectoryPath + File.separator + mp3FileName);
+
+		FileOutputStream outputStream;
+		try {
+			outputStream = new FileOutputStream(mp3File);
+		} catch (FileNotFoundException e) {
+			LOG.error(e.getMessage(), e);
+			throw new StatusException("500", "音频文件处理失败!");
+		}
+
+		try (InputStream is = file.getInputStream();
+				BufferedInputStream bis = new BufferedInputStream(is, 1024 * 10);) {
+			int read;
+			byte[] bytes = new byte[1024 * 4];
+			while ((read = bis.read(bytes, 0, 1024 * 4)) != -1) {
+				outputStream.write(bytes, 0, read);
+			}
+
+			// 上传到又拍云
+			// UpYun upYun = new UpYun(sysProperty.getBucketName(),
+			// sysProperty.getUserName(), sysProperty.getPassword());
+			// upYun.writeFile(sysProperty.getRadioUploadPath() + mp3FileName, mp3File,
+			// true);
+			// 通用存储
+			FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+			env.setRelativePath(sysProperty.getRadioUploadPath() + mp3FileName);
+			YunPathInfo pi = FileStorageUtil.saveFile("audioFile", env, mp3File, null);
+			IoUtils.removeFile(mp3File);
+
+			// 保存记录
+			QuestionAudio audio = new QuestionAudio(questionId, file.getOriginalFilename(), pi.getRelativePath());
+			questionAudioService.saveQuestionAudio(audio, user);
+		} catch (Exception e) {
+			LOG.error(e.getMessage(), e);
+			throw new StatusException("500", "音频文件保存失败!");
+		} finally {
+			try {
+				if (outputStream != null) {
+					outputStream.close();
+				}
+			} catch (IOException e) {
+				// ignore
+			}
+		}
+	}
+
+	/**
+	 * 音频文件插入到标签
+	 */
+	private void appendAudioTag(String fileName, String questionId) {
+		QuestionAudio questionAudio = questionAudioService.findByQuestionIdAndFileName(questionId, fileName);
+		if (questionAudio == null) {
+			return;
+		}
+
+		Question question = Model.of(quesRepo.findById(questionAudio.getQuestionId()));
+
+		// 正则匹配音频标签
+		Pattern audioPattern = Pattern.compile(String.format("<a id=\"[\\d,\\w]+\" name=\"%s\"></a>", fileName));
 		final String audioTag = String.format("<a id=\"%s\" name=\"%s\"></a>", questionAudio.getId(), fileName);
 
-        String numbers[] = fileName.split("_");
-        if (numbers[1].equals("1")) {
-            //处理题干
-            String quesBody = question.getQuesBody();
-            if (StringUtils.isBlank(quesBody)) {
-                question.setQuesBody("<p>" + audioTag + "</p>");
-            } else {
-                Matcher matcher = audioPattern.matcher(quesBody);
-                if (matcher.find()) {
-                    //已存在音频标签则直接替换
-                    question.setQuesBody(matcher.replaceAll(audioTag));
-                } else {
-                    //不存在音频标签则添加
-                    question.setQuesBody(quesBody + "<p>" + audioTag + "</p>");
-                }
-            }
-        } else {
-            //处理选项
-            for (QuesOption quesOption : question.getQuesOptions()) {
-                Integer optNumber = CommonUtils.characterToNumber(numbers[2]);
-                if (!quesOption.getNumber().equals(optNumber.toString())) {
-                    continue;
-                }
-
-                String optionBody = quesOption.getOptionBody();
-                if (StringUtils.isBlank(optionBody)) {
-                    quesOption.setOptionBody("<p>" + audioTag + "</p>");
-                    continue;
-                }
-
-                Matcher matcher = audioPattern.matcher(optionBody);
-                if (matcher.find()) {
-                    //已存在音频标签则直接替换
-                    quesOption.setOptionBody(matcher.replaceAll(audioTag));
-                } else {
-                    //不存在音频标签则添加
-                    quesOption.setOptionBody(optionBody + "<p>" + audioTag + "</p>");
-                }
-            }
-        }
+		String numbers[] = fileName.split("_");
+		if (numbers[1].equals("1")) {
+			// 处理题干
+			String quesBody = question.getQuesBody();
+			if (StringUtils.isBlank(quesBody)) {
+				question.setQuesBody("<p>" + audioTag + "</p>");
+			} else {
+				Matcher matcher = audioPattern.matcher(quesBody);
+				if (matcher.find()) {
+					// 已存在音频标签则直接替换
+					question.setQuesBody(matcher.replaceAll(audioTag));
+				} else {
+					// 不存在音频标签则添加
+					question.setQuesBody(quesBody + "<p>" + audioTag + "</p>");
+				}
+			}
+		} else {
+			// 处理选项
+			for (QuesOption quesOption : question.getQuesOptions()) {
+				Integer optNumber = CommonUtils.characterToNumber(numbers[2]);
+				if (!quesOption.getNumber().equals(optNumber.toString())) {
+					continue;
+				}
+
+				String optionBody = quesOption.getOptionBody();
+				if (StringUtils.isBlank(optionBody)) {
+					quesOption.setOptionBody("<p>" + audioTag + "</p>");
+					continue;
+				}
+
+				Matcher matcher = audioPattern.matcher(optionBody);
+				if (matcher.find()) {
+					// 已存在音频标签则直接替换
+					quesOption.setOptionBody(matcher.replaceAll(audioTag));
+				} else {
+					// 不存在音频标签则添加
+					quesOption.setOptionBody(optionBody + "<p>" + audioTag + "</p>");
+				}
+			}
+		}
 
-        question.setHasAudio(true);
-        quesRepo.save(question);
-    }
-
-    @Override
-    public Map<String, Object> getPaperPDF(String paperId, String type) {
-        PaperExp paperExp = getPaperDto(paperId);
-        Map<String, Object> map = new HashMap<>();
-        map.put("courseName", paperExp.getCourseName());
-        map.put("courseNo", paperExp.getCourseNo());
-        List<String> htmlList = new ArrayList<>();
-        if (paperExp.getPaperDetails() != null && paperExp.getPaperDetails().size() > 0) {
-            for (PaperDetailExp paperDetail : paperExp.getPaperDetails()) {
-                //添加大题标题
-                if (StringUtils.isBlank(paperDetail.getTitle())) {
-                    //htmlList.add(("<p>"+paperDetail.getCnNum()+"、"+paperDetail.getName()+"</p>").replaceAll("'", "&apos"));
-                    htmlList.add(("<p class='paperDetailTitle'>" + paperDetail.getCnNum() + "、" + paperDetail.getName() + "</p>").replaceAll("'", "&apos"));
-                } else {
-                    htmlList.add("<p class='paperDetailTitle'>" + paperDetail.getCnNum() + "、" + paperDetail.getName() + paperDetail.getTitle() + "</p>");
-                }
-                if (paperDetail.getPaperDetailUnits() != null && paperDetail.getPaperDetailUnits().size() > 0) {
-                    for (PaperDetailUnitExp paperDetailUnitExp : paperDetail.getPaperDetailUnits()) {
-                        Question question = paperDetailUnitExp.getQuestion();
-                        if (type.equals("paper")) {
-                            //添加题干
-                            String questionBody = CommonUtils.deleteHtmlP(paperDetailUnitExp.getNumber() + "." + question.getQuesBody() + "(" + paperDetailUnitExp.getScore() + "分)");
-                            htmlList.add("<p class='questionBody'>" + questionBody + "</p>");
-                            //判断是否为选择题
-                            if (paperDetailUnitExp.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION || paperDetailUnitExp.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                                for (QuesOption quesOption : question.getQuesOptions()) {
-                                    //添加选项
-                                    String questionOption = CommonUtils.deleteHtmlP(CommonUtils.getOptionNum(Integer.parseInt(quesOption.getNumber()) - 1) + "." + quesOption.getOptionBody());
-                                    htmlList.add("<p class='selectOption'>" + questionOption + "</p>");
-                                }
-                            }
-                            //判断是否为套题
-                            if (paperDetailUnitExp.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                                //得到子题
-                                List<Question> subQuestions = question.getSubQuestions();
-                                for (Question subQuestion : subQuestions) {
-                                    //添加子题题干
-                                    String subQuestionBody = CommonUtils.deleteHtmlP(subQuestion.getQuesParams().get("number") + "." + subQuestion.getQuesBody() + "(" + subQuestion.getScore() + "分)");
-                                    htmlList.add("<p class='questionBody'>" + subQuestionBody + "</p>");
-                                    //判断是否为选择题
-                                    if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION || subQuestion.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
-                                        for (QuesOption quesOption : subQuestion.getQuesOptions()) {
-                                            //添加选项
-                                            String questionOption = CommonUtils.deleteHtmlP(CommonUtils.getOptionNum(Integer.parseInt(quesOption.getNumber()) - 1) + "." + quesOption.getOptionBody());
-                                            htmlList.add("<p class='selectOption'>" + questionOption + "</p>");
-                                        }
-                                    }
-                                }
-                            }
-                        } else {
-                            //判断是否为套题
-                            if (paperDetailUnitExp.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
-                                //得到子题
-                                List<Question> subQuestions = question.getSubQuestions();
-                                for (Question subQuestion : subQuestions) {
-                                    //添加答案
-                                    String questionAnswer = CommonUtils.deleteHtmlP(subQuestion.getQuesParams().get("number") + "." + subQuestion.getQuesAnswer() + "(" + subQuestion.getScore() + "分)");
-                                    htmlList.add("<p class='questionAnswer'>" + questionAnswer + "</p>");
-                                }
-                            } else {
-                                //添加答案
-                                String questionAnswer = CommonUtils.deleteHtmlP(paperDetailUnitExp.getNumber() + "." + question.getQuesAnswer() + "(" + paperDetailUnitExp.getScore() + "分)");
-                                htmlList.add("<p class='questionAnswer'>" + questionAnswer + "</p>");
-                            }
-                        }
-
-                    }
-                }
-            }
-        }
-        map.put("htmlList", htmlList);
-        return map;
-    }
-
-    @SuppressWarnings("unused")
-    @Override
-    public String sendPrint(String paperId, String orgId, String examId) {
-        //查询原paper对象
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        SyncCoursePaperBean bean = new SyncCoursePaperBean();
-        bean.setOrgId(Long.valueOf(orgId));
-        bean.setExamId(Long.valueOf(examId));
-        Course course = courseService.getCourse(Long.valueOf(orgId), paper.getCourse().getCode());
-        bean.setCourseId(Long.valueOf(course.getId()));
-        bean.setCourseCode(paper.getCourse().getCode());
-        bean.setCourseName(paper.getCourse().getName());
-        bean.setPaperId(paper.getId());
-        bean.setPaperName(paper.getName());
-        SyncCoursePaperReq req = new SyncCoursePaperReq();
-        req.setBean(bean);
-        SyncCoursePaperResp resp = coursePaperCloudService.syncCoursePaper(req);
-        return "success";
-    }
-
-    @Override
-    public String findQuestionStructure(String paperId) throws Exception {
-        //根据paperId查询新的对象
-        ExportPaperAbstractService exportPaperAbstractService = (ExportPaperAbstractService) SpringContextHolder.getBean("exportPaperAbstractService");
-        DownloadPaperDto dto = new DownloadPaperDto();
-        dto.setPaperId(paperId);
-        dto.setSeqMode(PaperSeqMode.MODE3);
-        PaperExp paperExp = exportPaperAbstractService.initPaperExp(dto);
-        LOG.info("已经获取试卷");
-        //得到试卷中的客观大题
-        List<PaperDetailExp> objectiveDetails = exportPaperAbstractService.getAllObjectiveDetails(paperExp);
-        //客观题中如果有套题,要拆开
-        List<PaperDetailExp> newObjectiveDetails = buildPaperDetailExp(objectiveDetails);
-        //生成客观题数集合
-        List<ObjectiveQuestionStructure> objectiveQuestionStructureList = new ArrayList<>();
-        for (PaperDetailExp paperDetailExp : newObjectiveDetails) {
-            for (PaperDetailUnitExp unit : paperDetailExp.getPaperDetailUnits()) {
-                objectiveQuestionStructureList.add(new ObjectiveQuestionStructure(paperExp, paperDetailExp, unit, null));
-            }
-        }
-        LOG.info("生成客观题数集合");
-        //得到试卷中的主观大题
-        List<PaperDetailExp> subjectiveDetails = exportPaperAbstractService.getAllSubjectiveDetails(paperExp);
-        //生成主观题数集合
-        List<SubjectiveQuestionStructure> subjectiveQuestionStructureList = new ArrayList<>();
-        for (PaperDetailExp paperDetailExp : subjectiveDetails) {
-            for (PaperDetailUnitExp unit : paperDetailExp.getPaperDetailUnits()) {
-                subjectiveQuestionStructureList.add(new SubjectiveQuestionStructure(paperExp, paperDetailExp, unit, null));
-            }
-        }
-        LOG.info("生成主观题数集合");
-        PaperQuestionStructureInfo info = new PaperQuestionStructureInfo();
-        info.setObjectives(objectiveQuestionStructureList);
-        info.setSubjectives(subjectiveQuestionStructureList);
-        String jsonResp = JsonUtil.toJson(info);
-        LOG.info("jsonResp:" + jsonResp);
-        return jsonResp;
-    }
-
-    public List<PaperDetailExp> buildPaperDetailExp(List<PaperDetailExp> objectiveDetails) {
-        //1.得到所有客观题大题的类型
-        Set<QuesStructType> types = new HashSet<>();
-        for (PaperDetailExp paperDetailExp : objectiveDetails) {
-            types.add(QuesStructType.getQuesStructTypeById(paperDetailExp.getSortNumber()));
-        }
-        //2.生成新的大题集合
-        List<PaperDetailExp> newObjs = new ArrayList<>();
-        for (QuesStructType type : types) {
-            for (PaperDetailExp paperDetailExp : objectiveDetails) {
-                if (paperDetailExp.getSortNumber().equals(type.getId())) {
-                    //生成新的大题
-                    PaperDetailExp newPaperDetailExp = new PaperDetailExp();
-                    //生成新的小题
-                    List<PaperDetailUnitExp> newPaperDetailUnits = new ArrayList<>();
-                    //得到旧大题下的所有小题
-                    List<PaperDetailUnitExp> paperDetailUnits = paperDetailExp.getPaperDetailUnits();
-                    for (PaperDetailUnitExp paperDetailUnitExp : paperDetailUnits) {
-                        if (paperDetailUnitExp.getQuestionType() != QuesStructType.NESTED_ANSWER_QUESTION) {
-                            newPaperDetailUnits.add(paperDetailUnitExp);
-                        } else {
-                            List<Question> subQuestions = paperDetailUnitExp.getQuestion().getSubQuestions();
-                            if (subQuestions != null && subQuestions.size() > 0) {
-                                for (int i = 0; i < subQuestions.size(); i++) {
-                                    newPaperDetailUnits.add(new PaperDetailUnitExp(type, subQuestions.get(i)));
-                                }
-                            }
-                        }
-                    }
-                    //设置小题
-                    newPaperDetailExp.setPaperDetailUnits(newPaperDetailUnits);
-                    //设置大题题号
-                    newPaperDetailExp.setNumber(Integer.parseInt(type.getId() + ""));
-                    newPaperDetailExp.setSortNumber(paperDetailExp.getSortNumber());
-                    newObjs.add(newPaperDetailExp);
-                }
-            }
-        }
-        //刷一遍number
-        int num = 0;
-        for (QuesStructType type : types) {
-            for (int i = 0; i < newObjs.size(); i++) {
-                if (newObjs.get(i).getSortNumber().equals(type.getId())) {
-                    List<PaperDetailUnitExp> exps = newObjs.get(i).getPaperDetailUnits();
-                    for (int j = 0; j < exps.size(); j++) {
-                        num++;
-                        exps.get(j).setNumber(num);
-                    }
-                }
-            }
-        }
-        return newObjs;
-    }
-
-    @Override
-    public PaperExp getDownPaperExp(String paperId) {
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        // 创建paperDto
-        PaperExp paperExp = BeanCopierUtil.copyProperties(paper, PaperExp.class);
-        paperExp.setCourseName(paper.getCourse().getName());
-        paperExp.setCourseNo(paper.getCourse().getCode());
-        paperExp.setCourse(paper.getCourse());
-        // 获取大题
-        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
-        List<PaperDetailExp> paperDetailExps = new ArrayList<>();
-        for (PaperDetail paperDetail : paperDetails) {
-            PaperDetailExp paperDetailExp = new PaperDetailExp();
-            BeanUtils.copyProperties(paperDetail, paperDetailExp);
-            if (StringUtils.isBlank(paperDetailExp.getName())) {
-                paperDetailExp.setName("默认大题");
-            }
-            paperDetailExps.add(paperDetailExp);
-        }
-        // 封装小题
-        for (int i = 0; i < paperDetailExps.size(); i++) {
-            List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperDetailOrderByNumber(paperDetails.get(i));
-            if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
-                List<PaperDetailUnitExp> paperDetailUnitExps = new ArrayList<>();
-                for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-                    PaperDetailUnitExp paperDetailUnitExp = new PaperDetailUnitExp();
-                    BeanUtils.copyProperties(paperDetailUnit, paperDetailUnitExp);
-                    paperDetailUnitExps.add(paperDetailUnitExp);
-                }
-                paperDetailExps.get(i).setPaperDetailUnits(paperDetailUnitExps);
-                paperDetailExps.get(i).setUnitCount(paperDetailUnitExps.size());
-            } else {
-                paperDetailExps.get(i).setUnitCount(0);
-            }
-        }
-        paperExp.setPaperDetails(paperDetailExps);
-        if (paperExp.getPaperDetails() == null || paperExp.getPaperDetails().size() == 0) {
-            return paperExp;
-        }
-        int mainNum = 0;
-        List<PaperDetailExp> paperDetailExpList = paperExp.getPaperDetails();
-        for (PaperDetailExp paperDetail : paperDetailExpList) {
-            // 大题序号
-            paperDetail.setNumber(++mainNum);
-            paperDetail.setCnNum(CommonUtils.toCHNum(paperDetail.getNumber()));
-            if (paperDetail.getPaperDetailUnits() == null || paperDetail.getPaperDetailUnits().size() == 0) {
-                throw new StatusException("500", "第" + mainNum + "大题没有小题");
-            }
-            if (paperDetail != null && paperDetail.getPaperDetailUnits() != null && paperDetail.getPaperDetailUnits().size() > 0) {
-                for (PaperDetailUnitExp paperDetailUnit : paperDetail.getPaperDetailUnits()) {
-                    Question question = paperDetailUnit.getQuestion();
-                    question.setDifficultyDegree(question.getDifficultyDegree() * 10);
-                    if (question.getPublicity() == null || question.getPublicity() == true) {
-                        paperDetailUnit.setPublicity("公开");
-                    } else {
-                        paperDetailUnit.setPublicity("非公开");
-                    }
-                    if (StringUtils.isBlank(paperDetail.getQuesType())) {
-                        paperDetail.setQuesType(question.getQuestionType().getName());
-                    }
-                    //设置选项
-                    List<QuesOption> optionList = paperDetailUnit.getQuestion().getQuesOptions();
-                    if (optionList != null && optionList.size() > 0) {
-                        //                        int index = 0;
-                        //                        for (QuesOption quesOption : optionList) {
-                        //                            quesOption.setOptionBodyWord(setOptionNum(quesOption.getOptionBodyWord(), getOptionNum(index)));
-                        //                            index++;
-                        //                        }
-                    }
-                    if (question.getQuestionType() != QuesStructType.NESTED_ANSWER_QUESTION) {
-                        //给小题设置序号
-                        //                        question.setQuesBodyWord(setSubQuesNum(question.getQuesBodyWord(), minNum++));
-                        //                        question.setQuesAnswerWord(setAnswerNum(question.getQuesAnswerWord()));
-                    	fillProperty(question);
-                    	if (paperDetail.getFirstScore() == null) {
-                            if (question.getScore() == null) {
-                                paperDetail.setFirstScore(0d);
-                            } else {
-                                paperDetail.setFirstScore(question.getScore());
-                            }
-                        }
-                    } else {
-                        List<Question> subQuesList = question.getSubQuestions();
-                        // 套题小题设置序号
-                        if (subQuesList != null && subQuesList.size() > 0) {
-                            for (Question subQues : subQuesList) {
-                                subQues.setDifficultyDegree(subQues.getDifficultyDegree() * 10);
-                                //                                subQues.setQuesBodyWord(setSubQuesNum(subQues.getQuesBodyWord(), index++));
-                                //                                subQues.setQuesAnswerWord(setAnswerNum(subQues.getQuesAnswerWord()));
-                                if (paperDetail.getFirstScore() == null) {
-                                    if (subQues.getScore() == null) {
-                                        paperDetail.setFirstScore(0d);
-                                    } else {
-                                        paperDetail.setFirstScore(subQues.getScore());
-                                    }
-                                }
-                                fillProperty(subQues);
-                            }
-                        }
-                    }
-                    List<QuesProperty> quesProperties = question.getQuesProperties();
-                    if (quesProperties != null && quesProperties.size() > 0) {
-                        QuesProperty quesProperty = quesProperties.get(0);
-                        if (quesProperty.getFirstProperty() != null) {
-                            paperDetailUnit.setFirstName(quesProperty.getFirstProperty().getName());
-                            paperDetailUnit.setFirstCode(quesProperty.getFirstProperty().getCode());
-                        }
-                        if (quesProperty.getSecondProperty() != null) {
-                            paperDetailUnit.setSecondName(quesProperty.getSecondProperty().getName());
-                            paperDetailUnit.setSecondCode(quesProperty.getSecondProperty().getCode());
-                        }
-                    }
-                }
-            }
-        }
-        return paperExp;
-    }
-    
-    private void fillProperty(Question question) {
-    	List<QuesProperty> ret=new ArrayList<>();
-    	List<QuesProperty> quesProperties = question.getQuesProperties();
-        if (quesProperties != null && quesProperties.size() > 0) {
-        	for(QuesProperty quesProperty:quesProperties) {
-        		if (quesProperty.getFirstProperty() != null
-						&& quesProperty.getFirstProperty().getId() != null) {
-                	Property curProperty = Model.of(propertyRepo.findById(quesProperty.getFirstProperty().getId()));
-                	if(curProperty==null) {
-                		continue;
-                	}
-                	quesProperty.setFirstProperty(curProperty);
-                }
-                if (quesProperty.getSecondProperty() != null
-						&& quesProperty.getSecondProperty().getId() != null) {
-                	Property curProperty = Model.of(propertyRepo.findById(quesProperty.getSecondProperty().getId()));
-                	if(curProperty==null) {
-                		continue;
-                	}
-                	quesProperty.setSecondProperty(curProperty);
-                }
-                ret.add(quesProperty);
-        	}
-        }
-        question.setQuesProperties(ret);
-    }
-
-    /**
-     * 设置题号
-     *
-     * @param quesBodyWordMl
-     * @param num
-     * @return
-     * @throws Exception
-     */
-    //    public String setSubQuesNum(String quesBodyWordMl, int num) throws Exception {
-    //        String tmpStr = DocxProcessUtil.BODY_HEADER + quesBodyWordMl + DocxProcessUtil.BODY_TAIL;
-    //        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
-    //        List<Object> pList = body.getContent();
-    //        int index = 0;
-    //        for (Object pObj : pList) {
-    //            if (index > 0) {
-    //                break;
-    //            }
-    //            P p = (P) pObj;
-    //            List<Object> pContent = p.getContent();
-    //            R run = new R();
-    //            Text text = new Text();
-    //            text.setValue(num + ". ");
-    //            run.getContent().add(text);
-    //            pContent.add(0, run);
-    //            index++;
-    //        }
-    //        StringBuffer pWordMl = new StringBuffer();
-    //        for (Object pObj : pList) {
-    //            if (pObj instanceof P) {
-    //                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
-    //            }
-    //        }
-    //        return pWordMl.toString();
-    //    }
-
-    /**
-     * 将数字1,2,3,4转化成A,B,C,D
-     *
-     * @param number
-     * @return
-     */
-    public String getOptionNum(int number) {
-        char optionNum = (char) (65 + number);
-        return String.valueOf(optionNum);
-    }
-
-    /**
-     * 设置选项号
-     *
-     * @param optionWordMl
-     * @param num
-     * @return
-     * @throws Exception
-     */
-    //    public String setOptionNum(String optionWordMl, String num) throws Exception {
-    //        String tmpStr = DocxProcessUtil.BODY_HEADER + optionWordMl + DocxProcessUtil.BODY_TAIL;
-    //        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
-    //        List<Object> pList = body.getContent();
-    //        int index = 0;
-    //        for (Object pObj : pList) {
-    //            if (index > 0) {
-    //                break;
-    //            }
-    //            P p = (P) pObj;
-    //            List<Object> pContent = p.getContent();
-    //            R run = new R();
-    //            Text text = new Text();
-    //            text.setValue(num + ". ");
-    //            run.getContent().add(text);
-    //            pContent.add(0, run);
-    //            index++;
-    //        }
-    //        StringBuffer pWordMl = new StringBuffer();
-    //        for (Object pObj : pList) {
-    //            if (pObj instanceof P) {
-    //                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
-    //            }
-    //        }
-    //        return pWordMl.toString();
-    //    }
-
-    //    public String setAnswerNum(String quesBodyWordMl) throws Exception {
-    //        String tmpStr = DocxProcessUtil.BODY_HEADER + quesBodyWordMl + DocxProcessUtil.BODY_TAIL;
-    //        Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
-    //        List<Object> pList = body.getContent();
-    //        int index = 0;
-    //        for (Object pObj : pList) {
-    //            if (index > 0) {
-    //                break;
-    //            }
-    //            P p = (P) pObj;
-    //            List<Object> pContent = p.getContent();
-    //            R run = new R();
-    //            Text text = new Text();
-    //            text.setValue("[答案]:");
-    //            run.getContent().add(text);
-    //            pContent.add(0, run);
-    //            index++;
-    //        }
-    //        StringBuffer pWordMl = new StringBuffer();
-    //        for (Object pObj : pList) {
-    //            if (pObj instanceof P) {
-    //                pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
-    //            }
-    //        }
-    //        return pWordMl.toString();
-    //    }
-    @Override
-    public int getQuestionTypeNumbers(String paperId, Integer publicityType, Integer difficultyType) {
-        if (publicityType == null || difficultyType == null) {
-            return 0;
-        }
-        Boolean publicity = null;
-        String difficulty = null;
-        if (publicityType == 1) {
-            publicity = true;
-        } else {
-            publicity = false;
-        }
-        if (difficultyType == 1) {
-            difficulty = "易";
-        } else if (difficultyType == 2) {
-            difficulty = "中";
-        } else {
-            difficulty = "难";
-        }
-        String needType = publicity + "-" + difficulty;
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        List<PaperDetailUnit> units = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
-        if (units != null && units.size() > 0) {
-            int total = 0;
-            for (PaperDetailUnit unit : units) {
-                if (unit.getQuestion().getPublicity() == null) {
-                    unit.getQuestion().setPublicity(true);
-                }
-                if (unit.getQuestion().getDifficulty() == null) {
-                    unit.getQuestion().setDifficulty("中");
-                }
-                String factType = unit.getQuestion().getPublicity() + "-" + unit.getQuestion().getDifficulty();
-                if (needType.equals(factType)) {
-                    total += 1;
-                }
-            }
-            return total;
-        }
-        return 0;
-    }
+		question.setHasAudio(true);
+		quesRepo.save(question);
+	}
 
-    @Override
-    public double getQuestionTypeScore(String paperId, Integer publicityType, Integer difficultyType) {
-        if (publicityType == null || difficultyType == null) {
-            return 0;
-        }
-        Boolean publicity = null;
-        String difficulty = null;
-        if (publicityType == 1) {
-            publicity = true;
-        } else {
-            publicity = false;
-        }
-        if (difficultyType == 1) {
-            difficulty = "易";
-        } else if (difficultyType == 2) {
-            difficulty = "中";
-        } else {
-            difficulty = "难";
-        }
-        String needType = publicity + "-" + difficulty;
-        Paper paper = Model.of(paperRepo.findById(paperId));
-        List<PaperDetailUnit> units = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
-        if (units != null && units.size() > 0) {
-            double total = 0;
-            BigDecimal b1 = BigDecimal.valueOf(total);
-            for (PaperDetailUnit unit : units) {
-                if (unit.getQuestion().getPublicity() == null) {
-                    unit.getQuestion().setPublicity(true);
-                }
-                if (unit.getQuestion().getDifficulty() == null) {
-                    unit.getQuestion().setDifficulty("中");
-                }
-                String factType = unit.getQuestion().getPublicity() + "-" + unit.getQuestion().getDifficulty();
-                if (needType.equals(factType)) {
-                    BigDecimal b2 = BigDecimal.valueOf(unit.getScore());
-                    b1 = b1.add(b2);
-                }
-            }
-            return b1.doubleValue();
-        }
-        return 0;
-    }
-
-    private void clearPaperCache(String paperId) {
-        //清理与当前试卷相关的缓存
-        //        final String patternKey = CACHE_KEY_PAPER + "*" + paperId;
-        //        Set<String> keys = redisTemplate.keys(patternKey);
-        //        if (CollectionUtils.isNotEmpty(keys)) {
-        //            redisTemplate.delete(keys);
-        //        }
-        extractConfigPaperCache.remove(paperId);
-        basePaperCache.remove(paperId);
-    }
-
-    private void clearQuestionCache(String questionId) {
-        //清理与当前试题相关的缓存
-        //        final String patternKey = CACHE_KEY_QUESTION + "*" + questionId;
-        //        Set<String> keys = redisTemplate.keys(patternKey);
-        //        if (CollectionUtils.isNotEmpty(keys)) {
-        //            redisTemplate.delete(keys);
-        //        }
-        questionCache.remove(questionId);
-        questionAnswerCache.remove(questionId);
-    }
-
-    @Override
-    public List<PaperAnswerDomain> answerExport(Paper paper) {
-        List<PaperAnswerDomain> ret = new ArrayList<PaperAnswerDomain>();
-        List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
-        if (paperDetails != null && paperDetails.size() > 0) {
-            for (PaperDetail paperDetail : paperDetails) {
-                List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperDetailOrderByNumber(paperDetail);
-                if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
-                    for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
-                        Question ques = paperDetailUnit.getQuestion();
-                        if (QuesStructType.NESTED_ANSWER_QUESTION.equals(ques.getQuestionType())) {
-                            List<Question> subques = ques.getSubQuestions();
-                            if (subques != null && subques.size() > 0) {
-                                for (int i = 0; i < subques.size(); i++) {
-                                    Question subq = subques.get(i);
-                                    PaperAnswerDomain domain = new PaperAnswerDomain();
-                                    domain.setName(paperDetail.getName());
-                                    domain.setNumber(paperDetail.getNumber());
-                                    domain.setSubNumber(paperDetailUnit.getNumber());
-                                    domain.setSubType(subq.getQuestionType().getName());
-                                    domain.setChildNumber(i + 1);
-                                    domain.setAnswer(subq.getQuesAnswer());
-                                    ret.add(domain);
-                                }
-                            }
-                        } else {
-                            PaperAnswerDomain domain = new PaperAnswerDomain();
-                            domain.setName(paperDetail.getName());
-                            domain.setNumber(paperDetail.getNumber());
-                            domain.setSubNumber(paperDetailUnit.getNumber());
-                            domain.setSubType(paperDetailUnit.getQuestionType().getName());
-                            domain.setAnswer(ques.getQuesAnswer());
-                            ret.add(domain);
-                        }
-                    }
-                }
-            }
-        }
-        return ret;
-    }
-
-    @Override
-    public void answerImport(Paper paper, MultipartFile dataFile) {
-        File file = new File(systemProperties.getTempDataDir() + File.separator + UUID.randomUUID() + ".xlsx");
-        try {
-            file.createNewFile();
-            dataFile.transferTo(file);
-            List<String[]> lineList = getData(file);
-            if (lineList == null || lineList.size() == 0) {
-                throw new StatusException("500", "没有导入的数据");
-            }
-            List<PaperAnswerDomain> ret = new ArrayList<PaperAnswerDomain>();
-            for (int i = 0; i < lineList.size(); i++) {
-                String[] line = lineList.get(i);
-                PaperAnswerDomain domain = new PaperAnswerDomain();
-                domain.setNumber(getNumber(trimAndNullIfBlank(line[0])));
-                domain.setName(trimAndNullIfBlank(line[1]));
-                domain.setSubNumber(getNumber(trimAndNullIfBlank(line[2])));
-                domain.setChildNumber(getNumber(trimAndNullIfBlank(line[3])));
-                domain.setSubType(trimAndNullIfBlank(line[4]));
-                domain.setAnswer(trimAndNullIfBlank(line[5]));
-                ret.add(domain);
-            }
-            if (ret == null || ret.size() == 0) {
-                throw new StatusException("500", "没有导入的数据");
-            }
-            diposeAnswer(paper, ret);
-        } catch (StatusException e) {
-            throw e;
-        } catch (Exception e) {
-            throw new StatusException("500", "导入答案失败:" + e.getMessage(), e);
-        } finally {
-            FileUtil.deleteFile(file.getAbsolutePath());
-        }
-    }
-
-    private List<String[]> getData(File file) {
-        XSSFWorkbook wb = null;
-        try {
-            try {
-                wb = new XSSFWorkbook(file);
-            } catch (Exception e) {
-                throw new StatusException("500", "文件类型错误");
-            }
-            List<String[]> outerList = new ArrayList<String[]>();
-            XSSFSheet sheet = wb.getSheetAt(0);
-            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
-                String[] innerList = new String[6];
-                XSSFRow row = sheet.getRow(i);
-                for (int j = 0; j < 6; j++) {
-                    XSSFCell cell = row.getCell(j);
-                    if (cell != null) {
-                        String cellinfo = cell.getStringCellValue();
-                        innerList[j] = cellinfo;
-                    } else {
-                        innerList[j] = "";
-                    }
-                }
-                if (!isEmpty(innerList)) {
-                    if (StringUtils.isEmpty(innerList[0])) {
-                        throw new StatusException("500", "文件类型错误" + (i + 1) + "行大题号不能为空");
-                    }
-                    if (StringUtils.isEmpty(innerList[2])) {
-                        throw new StatusException("500", "文件类型错误" + (i + 1) + "行小题号不能为空");
-                    }
-                    outerList.add(innerList);
-                }
-            }
-            return outerList;
-        } finally {
-            if (wb != null) {
-                try {
-                    wb.close();
-                } catch (IOException e) {
-                    LOG.debug("wb.close() error " + e);
-                }
-            }
-        }
+	@Override
+	public Map<String, Object> getPaperPDF(String paperId, String type) {
+		PaperExp paperExp = getPaperDto(paperId);
+		Map<String, Object> map = new HashMap<>();
+		map.put("courseName", paperExp.getCourseName());
+		map.put("courseNo", paperExp.getCourseNo());
+		List<String> htmlList = new ArrayList<>();
+		if (paperExp.getPaperDetails() != null && paperExp.getPaperDetails().size() > 0) {
+			for (PaperDetailExp paperDetail : paperExp.getPaperDetails()) {
+				// 添加大题标题
+				if (StringUtils.isBlank(paperDetail.getTitle())) {
+					// htmlList.add(("<p>"+paperDetail.getCnNum()+"、"+paperDetail.getName()+"</p>").replaceAll("'",
+					// "&apos"));
+					htmlList.add(("<p class='paperDetailTitle'>" + paperDetail.getCnNum() + "、" + paperDetail.getName()
+							+ "</p>").replaceAll("'", "&apos"));
+				} else {
+					htmlList.add("<p class='paperDetailTitle'>" + paperDetail.getCnNum() + "、" + paperDetail.getName()
+							+ paperDetail.getTitle() + "</p>");
+				}
+				if (paperDetail.getPaperDetailUnits() != null && paperDetail.getPaperDetailUnits().size() > 0) {
+					for (PaperDetailUnitExp paperDetailUnitExp : paperDetail.getPaperDetailUnits()) {
+						Question question = paperDetailUnitExp.getQuestion();
+						if (type.equals("paper")) {
+							// 添加题干
+							String questionBody = CommonUtils.deleteHtmlP(paperDetailUnitExp.getNumber() + "."
+									+ question.getQuesBody() + "(" + paperDetailUnitExp.getScore() + "分)");
+							htmlList.add("<p class='questionBody'>" + questionBody + "</p>");
+							// 判断是否为选择题
+							if (paperDetailUnitExp.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+									|| paperDetailUnitExp
+											.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+								for (QuesOption quesOption : question.getQuesOptions()) {
+									// 添加选项
+									String questionOption = CommonUtils.deleteHtmlP(
+											CommonUtils.getOptionNum(Integer.parseInt(quesOption.getNumber()) - 1) + "."
+													+ quesOption.getOptionBody());
+									htmlList.add("<p class='selectOption'>" + questionOption + "</p>");
+								}
+							}
+							// 判断是否为套题
+							if (paperDetailUnitExp.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+								// 得到子题
+								List<Question> subQuestions = question.getSubQuestions();
+								for (Question subQuestion : subQuestions) {
+									// 添加子题题干
+									String subQuestionBody = CommonUtils
+											.deleteHtmlP(subQuestion.getQuesParams().get("number") + "."
+													+ subQuestion.getQuesBody() + "(" + subQuestion.getScore() + "分)");
+									htmlList.add("<p class='questionBody'>" + subQuestionBody + "</p>");
+									// 判断是否为选择题
+									if (subQuestion.getQuestionType() == QuesStructType.SINGLE_ANSWER_QUESTION
+											|| subQuestion
+													.getQuestionType() == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+										for (QuesOption quesOption : subQuestion.getQuesOptions()) {
+											// 添加选项
+											String questionOption = CommonUtils.deleteHtmlP(CommonUtils
+													.getOptionNum(Integer.parseInt(quesOption.getNumber()) - 1) + "."
+													+ quesOption.getOptionBody());
+											htmlList.add("<p class='selectOption'>" + questionOption + "</p>");
+										}
+									}
+								}
+							}
+						} else {
+							// 判断是否为套题
+							if (paperDetailUnitExp.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+								// 得到子题
+								List<Question> subQuestions = question.getSubQuestions();
+								for (Question subQuestion : subQuestions) {
+									// 添加答案
+									String questionAnswer = CommonUtils
+											.deleteHtmlP(subQuestion.getQuesParams().get("number") + "."
+													+ subQuestion.getQuesAnswer() + "(" + subQuestion.getScore()
+													+ "分)");
+									htmlList.add("<p class='questionAnswer'>" + questionAnswer + "</p>");
+								}
+							} else {
+								// 添加答案
+								String questionAnswer = CommonUtils.deleteHtmlP(paperDetailUnitExp.getNumber() + "."
+										+ question.getQuesAnswer() + "(" + paperDetailUnitExp.getScore() + "分)");
+								htmlList.add("<p class='questionAnswer'>" + questionAnswer + "</p>");
+							}
+						}
+
+					}
+				}
+			}
+		}
+		map.put("htmlList", htmlList);
+		return map;
+	}
 
+	@SuppressWarnings("unused")
+	@Override
+	public String sendPrint(String paperId, String orgId, String examId) {
+		// 查询原paper对象
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		SyncCoursePaperBean bean = new SyncCoursePaperBean();
+		bean.setOrgId(Long.valueOf(orgId));
+		bean.setExamId(Long.valueOf(examId));
+		Course course = courseService.getCourse(Long.valueOf(orgId), paper.getCourse().getCode());
+		bean.setCourseId(Long.valueOf(course.getId()));
+		bean.setCourseCode(paper.getCourse().getCode());
+		bean.setCourseName(paper.getCourse().getName());
+		bean.setPaperId(paper.getId());
+		bean.setPaperName(paper.getName());
+		SyncCoursePaperReq req = new SyncCoursePaperReq();
+		req.setBean(bean);
+		SyncCoursePaperResp resp = coursePaperCloudService.syncCoursePaper(req);
+		return "success";
+	}
 
-    }
+	@Override
+	public String findQuestionStructure(String paperId) throws Exception {
+		// 根据paperId查询新的对象
+		ExportPaperAbstractService exportPaperAbstractService = (ExportPaperAbstractService) SpringContextHolder
+				.getBean("exportPaperAbstractService");
+		DownloadPaperDto dto = new DownloadPaperDto();
+		dto.setPaperId(paperId);
+		dto.setSeqMode(PaperSeqMode.MODE3);
+		PaperExp paperExp = exportPaperAbstractService.initPaperExp(dto);
+		LOG.info("已经获取试卷");
+		// 得到试卷中的客观大题
+		List<PaperDetailExp> objectiveDetails = exportPaperAbstractService.getAllObjectiveDetails(paperExp);
+		// 客观题中如果有套题,要拆开
+		List<PaperDetailExp> newObjectiveDetails = buildPaperDetailExp(objectiveDetails);
+		// 生成客观题数集合
+		List<ObjectiveQuestionStructure> objectiveQuestionStructureList = new ArrayList<>();
+		for (PaperDetailExp paperDetailExp : newObjectiveDetails) {
+			for (PaperDetailUnitExp unit : paperDetailExp.getPaperDetailUnits()) {
+				objectiveQuestionStructureList
+						.add(new ObjectiveQuestionStructure(paperExp, paperDetailExp, unit, null));
+			}
+		}
+		LOG.info("生成客观题数集合");
+		// 得到试卷中的主观大题
+		List<PaperDetailExp> subjectiveDetails = exportPaperAbstractService.getAllSubjectiveDetails(paperExp);
+		// 生成主观题数集合
+		List<SubjectiveQuestionStructure> subjectiveQuestionStructureList = new ArrayList<>();
+		for (PaperDetailExp paperDetailExp : subjectiveDetails) {
+			for (PaperDetailUnitExp unit : paperDetailExp.getPaperDetailUnits()) {
+				subjectiveQuestionStructureList
+						.add(new SubjectiveQuestionStructure(paperExp, paperDetailExp, unit, null));
+			}
+		}
+		LOG.info("生成主观题数集合");
+		PaperQuestionStructureInfo info = new PaperQuestionStructureInfo();
+		info.setObjectives(objectiveQuestionStructureList);
+		info.setSubjectives(subjectiveQuestionStructureList);
+		String jsonResp = JsonUtil.toJson(info);
+		LOG.info("jsonResp:" + jsonResp);
+		return jsonResp;
+	}
 
-    private boolean isEmpty(String[] ss) {
-        for (String s : ss) {
-            if (StringUtils.isNotEmpty(s)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private void diposeAnswer(Paper paper, List<PaperAnswerDomain> domains) {
-        Map<String, PaperAnswerDomain> answerMap = new LinkedHashMap<String, PaperAnswerDomain>();
-        for (PaperAnswerDomain domain : domains) {
-            String key = null;
-            if (domain.getChildNumber() != null) {
-                key = domain.getSubNumber() + "-" + domain.getChildNumber();
-            } else {
-                key = domain.getSubNumber().toString();
-            }
-            if (answerMap.get(key) == null) {
-                answerMap.put(key, domain);
-            } else {
-                throw new StatusException("500", "导入数据中第" + domain.getSubNumber() + "小题" + (domain.getChildNumber() != null ? "第" + domain.getChildNumber() + "子题" : "") + "重复");
-            }
-        }
+	public List<PaperDetailExp> buildPaperDetailExp(List<PaperDetailExp> objectiveDetails) {
+		// 1.得到所有客观题大题的类型
+		Set<QuesStructType> types = new HashSet<>();
+		for (PaperDetailExp paperDetailExp : objectiveDetails) {
+			types.add(QuesStructType.getQuesStructTypeById(paperDetailExp.getSortNumber()));
+		}
+		// 2.生成新的大题集合
+		List<PaperDetailExp> newObjs = new ArrayList<>();
+		for (QuesStructType type : types) {
+			for (PaperDetailExp paperDetailExp : objectiveDetails) {
+				if (paperDetailExp.getSortNumber().equals(type.getId())) {
+					// 生成新的大题
+					PaperDetailExp newPaperDetailExp = new PaperDetailExp();
+					// 生成新的小题
+					List<PaperDetailUnitExp> newPaperDetailUnits = new ArrayList<>();
+					// 得到旧大题下的所有小题
+					List<PaperDetailUnitExp> paperDetailUnits = paperDetailExp.getPaperDetailUnits();
+					for (PaperDetailUnitExp paperDetailUnitExp : paperDetailUnits) {
+						if (paperDetailUnitExp.getQuestionType() != QuesStructType.NESTED_ANSWER_QUESTION) {
+							newPaperDetailUnits.add(paperDetailUnitExp);
+						} else {
+							List<Question> subQuestions = paperDetailUnitExp.getQuestion().getSubQuestions();
+							if (subQuestions != null && subQuestions.size() > 0) {
+								for (int i = 0; i < subQuestions.size(); i++) {
+									newPaperDetailUnits.add(new PaperDetailUnitExp(type, subQuestions.get(i)));
+								}
+							}
+						}
+					}
+					// 设置小题
+					newPaperDetailExp.setPaperDetailUnits(newPaperDetailUnits);
+					// 设置大题题号
+					newPaperDetailExp.setNumber(Integer.parseInt(type.getId() + ""));
+					newPaperDetailExp.setSortNumber(paperDetailExp.getSortNumber());
+					newObjs.add(newPaperDetailExp);
+				}
+			}
+		}
+		// 刷一遍number
+		int num = 0;
+		for (QuesStructType type : types) {
+			for (int i = 0; i < newObjs.size(); i++) {
+				if (newObjs.get(i).getSortNumber().equals(type.getId())) {
+					List<PaperDetailUnitExp> exps = newObjs.get(i).getPaperDetailUnits();
+					for (int j = 0; j < exps.size(); j++) {
+						num++;
+						exps.get(j).setNumber(num);
+					}
+				}
+			}
+		}
+		return newObjs;
+	}
 
-        Map<String, Question> quesMap = new LinkedHashMap<String, Question>();
-        List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperIdOrderByNumber(paper.getId());
-        for (PaperDetailUnit unit : paperDetailUnits) {
-            Question qu = unit.getQuestion();
-            if (QuesStructType.NESTED_ANSWER_QUESTION.equals(qu.getQuestionType())) {
-                for (int i = 0; i < unit.getQuestion().getSubQuestions().size(); i++) {
-                    quesMap.put(unit.getNumber() + "-" + (i + 1), unit.getQuestion().getSubQuestions().get(i));
-                }
-            } else {
-                quesMap.put(unit.getNumber().toString(), qu);
-            }
-        }
+	@Override
+	public PaperExp getDownPaperExp(String paperId) {
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		// 创建paperDto
+		PaperExp paperExp = BeanCopierUtil.copyProperties(paper, PaperExp.class);
+		paperExp.setCourseName(paper.getCourse().getName());
+		paperExp.setCourseNo(paper.getCourse().getCode());
+		paperExp.setCourse(paper.getCourse());
+		// 获取大题
+		List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
+		List<PaperDetailExp> paperDetailExps = new ArrayList<>();
+		for (PaperDetail paperDetail : paperDetails) {
+			PaperDetailExp paperDetailExp = new PaperDetailExp();
+			BeanUtils.copyProperties(paperDetail, paperDetailExp);
+			if (StringUtils.isBlank(paperDetailExp.getName())) {
+				paperDetailExp.setName("默认大题");
+			}
+			paperDetailExps.add(paperDetailExp);
+		}
+		// 封装小题
+		for (int i = 0; i < paperDetailExps.size(); i++) {
+			List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo
+					.findByPaperDetailOrderByNumber(paperDetails.get(i));
+			if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
+				List<PaperDetailUnitExp> paperDetailUnitExps = new ArrayList<>();
+				for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+					PaperDetailUnitExp paperDetailUnitExp = new PaperDetailUnitExp();
+					BeanUtils.copyProperties(paperDetailUnit, paperDetailUnitExp);
+					paperDetailUnitExps.add(paperDetailUnitExp);
+				}
+				paperDetailExps.get(i).setPaperDetailUnits(paperDetailUnitExps);
+				paperDetailExps.get(i).setUnitCount(paperDetailUnitExps.size());
+			} else {
+				paperDetailExps.get(i).setUnitCount(0);
+			}
+		}
+		paperExp.setPaperDetails(paperDetailExps);
+		if (paperExp.getPaperDetails() == null || paperExp.getPaperDetails().size() == 0) {
+			return paperExp;
+		}
+		int mainNum = 0;
+		List<PaperDetailExp> paperDetailExpList = paperExp.getPaperDetails();
+		for (PaperDetailExp paperDetail : paperDetailExpList) {
+			// 大题序号
+			paperDetail.setNumber(++mainNum);
+			paperDetail.setCnNum(CommonUtils.toCHNum(paperDetail.getNumber()));
+			if (paperDetail.getPaperDetailUnits() == null || paperDetail.getPaperDetailUnits().size() == 0) {
+				throw new StatusException("500", "第" + mainNum + "大题没有小题");
+			}
+			if (paperDetail != null && paperDetail.getPaperDetailUnits() != null
+					&& paperDetail.getPaperDetailUnits().size() > 0) {
+				for (PaperDetailUnitExp paperDetailUnit : paperDetail.getPaperDetailUnits()) {
+					Question question = paperDetailUnit.getQuestion();
+					question.setDifficultyDegree(question.getDifficultyDegree() * 10);
+					if (question.getPublicity() == null || question.getPublicity() == true) {
+						paperDetailUnit.setPublicity("公开");
+					} else {
+						paperDetailUnit.setPublicity("非公开");
+					}
+					if (StringUtils.isBlank(paperDetail.getQuesType())) {
+						paperDetail.setQuesType(question.getQuestionType().getName());
+					}
+					// 设置选项
+					List<QuesOption> optionList = paperDetailUnit.getQuestion().getQuesOptions();
+					if (optionList != null && optionList.size() > 0) {
+						// int index = 0;
+						// for (QuesOption quesOption : optionList) {
+						// quesOption.setOptionBodyWord(setOptionNum(quesOption.getOptionBodyWord(),
+						// getOptionNum(index)));
+						// index++;
+						// }
+					}
+					if (question.getQuestionType() != QuesStructType.NESTED_ANSWER_QUESTION) {
+						// 给小题设置序号
+						// question.setQuesBodyWord(setSubQuesNum(question.getQuesBodyWord(),
+						// minNum++));
+						// question.setQuesAnswerWord(setAnswerNum(question.getQuesAnswerWord()));
+						fillProperty(question);
+						if (paperDetail.getFirstScore() == null) {
+							if (question.getScore() == null) {
+								paperDetail.setFirstScore(0d);
+							} else {
+								paperDetail.setFirstScore(question.getScore());
+							}
+						}
+					} else {
+						List<Question> subQuesList = question.getSubQuestions();
+						// 套题小题设置序号
+						if (subQuesList != null && subQuesList.size() > 0) {
+							for (Question subQues : subQuesList) {
+								subQues.setDifficultyDegree(subQues.getDifficultyDegree() * 10);
+								// subQues.setQuesBodyWord(setSubQuesNum(subQues.getQuesBodyWord(), index++));
+								// subQues.setQuesAnswerWord(setAnswerNum(subQues.getQuesAnswerWord()));
+								if (paperDetail.getFirstScore() == null) {
+									if (subQues.getScore() == null) {
+										paperDetail.setFirstScore(0d);
+									} else {
+										paperDetail.setFirstScore(subQues.getScore());
+									}
+								}
+								fillProperty(subQues);
+							}
+						}
+					}
+					List<QuesProperty> quesProperties = question.getQuesProperties();
+					if (quesProperties != null && quesProperties.size() > 0) {
+						QuesProperty quesProperty = quesProperties.get(0);
+						if (quesProperty.getFirstProperty() != null) {
+							paperDetailUnit.setFirstName(quesProperty.getFirstProperty().getName());
+							paperDetailUnit.setFirstCode(quesProperty.getFirstProperty().getCode());
+						}
+						if (quesProperty.getSecondProperty() != null) {
+							paperDetailUnit.setSecondName(quesProperty.getSecondProperty().getName());
+							paperDetailUnit.setSecondCode(quesProperty.getSecondProperty().getCode());
+						}
+					}
+				}
+			}
+		}
+		return paperExp;
+	}
 
-        Set<String> saveQuestion = new HashSet<String>();
-        for (String key : answerMap.keySet()) {
-            PaperAnswerDomain domain = answerMap.get(key);
-            Question qu = quesMap.get(key);
-            if (qu == null) {
-                answerFomatErr(domain.getSubNumber(), domain.getChildNumber(), "试题信息不存在");
-            }
-            saveQuestion.add(domain.getSubNumber().toString());
-        }
+	private void fillProperty(Question question) {
+		List<QuesProperty> ret = new ArrayList<>();
+		List<QuesProperty> quesProperties = question.getQuesProperties();
+		if (quesProperties != null && quesProperties.size() > 0) {
+			for (QuesProperty quesProperty : quesProperties) {
+				if (quesProperty.getFirstProperty() != null && quesProperty.getFirstProperty().getId() != null) {
+					Property curProperty = Model.of(propertyRepo.findById(quesProperty.getFirstProperty().getId()));
+					if (curProperty == null) {
+						continue;
+					}
+					quesProperty.setFirstProperty(curProperty);
+				}
+				if (quesProperty.getSecondProperty() != null && quesProperty.getSecondProperty().getId() != null) {
+					Property curProperty = Model.of(propertyRepo.findById(quesProperty.getSecondProperty().getId()));
+					if (curProperty == null) {
+						continue;
+					}
+					quesProperty.setSecondProperty(curProperty);
+				}
+				ret.add(quesProperty);
+			}
+		}
+		question.setQuesProperties(ret);
+	}
 
-        Set<String> updateQuesIds = new HashSet<String>();
-        List<Question> ques = new ArrayList<Question>();
-        for (PaperDetailUnit unit : paperDetailUnits) {
-            Question qu = unit.getQuestion();
-            if (saveQuestion.contains(unit.getNumber().toString())) {
-                if (QuesStructType.NESTED_ANSWER_QUESTION.equals(qu.getQuestionType())) {
-                    for (int i = 0; i < unit.getQuestion().getSubQuestions().size(); i++) {
-                        if (answerMap.get(unit.getNumber() + "-" + (i + 1)) != null) {
-                            checkAndSetAnswer(unit.getQuestion().getSubQuestions().get(i), answerMap.get(unit.getNumber() + "-" + (i + 1)).getAnswer(), unit.getNumber(), i + 1);
-                            updateQuesIds.add(qu.getId());
-                        }
-                    }
-                } else {
-                    if (answerMap.get(unit.getNumber().toString()) != null) {
-                        checkAndSetAnswer(qu, answerMap.get(unit.getNumber().toString()).getAnswer(), unit.getNumber(), null);
-                        updateQuesIds.add(qu.getId());
-                    }
-                }
-            }
-            ques.add(qu);
-        }
-        quesRepo.saveAll(ques);
-        //清除缓存
-    	for(String qid:updateQuesIds) {
-    		paperDetailUnitService.clearQuestionCache(qid);
-        }
-    }
+	/**
+	 * 设置题号
+	 *
+	 * @param quesBodyWordMl
+	 * @param num
+	 * @return
+	 * @throws Exception
+	 */
+	// public String setSubQuesNum(String quesBodyWordMl, int num) throws Exception
+	// {
+	// String tmpStr = DocxProcessUtil.BODY_HEADER + quesBodyWordMl +
+	// DocxProcessUtil.BODY_TAIL;
+	// Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
+	// List<Object> pList = body.getContent();
+	// int index = 0;
+	// for (Object pObj : pList) {
+	// if (index > 0) {
+	// break;
+	// }
+	// P p = (P) pObj;
+	// List<Object> pContent = p.getContent();
+	// R run = new R();
+	// Text text = new Text();
+	// text.setValue(num + ". ");
+	// run.getContent().add(text);
+	// pContent.add(0, run);
+	// index++;
+	// }
+	// StringBuffer pWordMl = new StringBuffer();
+	// for (Object pObj : pList) {
+	// if (pObj instanceof P) {
+	// pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
+	// }
+	// }
+	// return pWordMl.toString();
+	// }
+
+	/**
+	 * 将数字1,2,3,4转化成A,B,C,D
+	 *
+	 * @param number
+	 * @return
+	 */
+	public String getOptionNum(int number) {
+		char optionNum = (char) (65 + number);
+		return String.valueOf(optionNum);
+	}
 
-    private void checkAndSetAnswer(Question question, String answer, Integer subNum, Integer childNum) {
-        String fomatErrMsg = "答案格式错误";
-        if (answer != null) {
-            answer = answer.trim();
-        }
-        if (StringUtils.isBlank(answer)) {
-            return;
-        }
-        if (QuesStructType.SINGLE_ANSWER_QUESTION.equals(question.getQuestionType())
-                || QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(question.getQuestionType())) {
-            answer = answer.replaceAll("\\s*", "");
-            if (QuesStructType.SINGLE_ANSWER_QUESTION.equals(question.getQuestionType())) {
-                if (answer.length() > 1) {
-                    answerFomatErr(subNum, childNum, fomatErrMsg);
-                }
-            }
-            String[] pAnswerArray;
-            if (answer.indexOf(",") != -1) {
-                pAnswerArray = answer.split(",");
-            } else {
-                pAnswerArray = answer.split("");
-            }
-            List<QuesOption> options = question.getQuesOptions();
-            List<String> optionNumList = new ArrayList<>();
-            for (QuesOption quesOption : options) {
-                Integer numInteger = Integer.parseInt(quesOption.getNumber());
-                char word = (char) (numInteger + 64);
-                optionNumList.add(word + "");
-            }
-            for (String option : pAnswerArray) {
-                String pattern = "[A-Z]";
-                if (!Pattern.matches(pattern, option)) {
-                    answerFomatErr(subNum, childNum, fomatErrMsg);
-                }
-                if (!optionNumList.contains(option)) {
-                    answerFomatErr(subNum, childNum, "答案选项不存在");
-                }
-            }
-            if(pAnswerArray.length>1) {
-            	question.setQuesAnswer(StringUtils.join(pAnswerArray, ","));
-            }else {
-            	question.setQuesAnswer(answer);
-            }
-            processSelectOption(question);
-        } else if (QuesStructType.BOOL_ANSWER_QUESTION.equals(question.getQuestionType())) {
-            if (!answer.equals("正确") && !answer.equals("错误")) {
-                answerFomatErr(subNum, childNum, fomatErrMsg);
-            }
-            question.setQuesAnswer(answer);
-        } else if (QuesStructType.FILL_BLANK_QUESTION.equals(question.getQuestionType())) {
-            String body = question.getQuesBody();
-            Document bodyDoc = Jsoup.parse(body);
-            String bodyText = bodyDoc.body().html();
-            Document answerDoc = null;
-            try {
-                answerDoc = Jsoup.parse(answer);
-            } catch (Exception e) {
-                answerFomatErr(subNum, childNum, "答案格式html解析错误");
-            }
-            if (answerDoc.body().childrenSize() != 0) {
-                String answerText = answerDoc.body().html();
-                if (StringUtils.isNotBlank(answerText)) {
-                    if (getSubStringCount(bodyText, "###") != answerText.split("##").length) {
-                        answerFomatErr(subNum, childNum, "题干空格数量和答案数量不一致");
-                    }
-                }
-            }
-            question.setQuesAnswer(answer);
-        } else if (QuesStructType.TEXT_ANSWER_QUESTION.equals(question.getQuestionType())) {
-            try {
-                Jsoup.parse(answer);
-            } catch (Exception e) {
-                answerFomatErr(subNum, childNum, "答案格式html解析错误");
-            }
-            question.setQuesAnswer(answer);
-        }
+	/**
+	 * 设置选项号
+	 *
+	 * @param optionWordMl
+	 * @param num
+	 * @return
+	 * @throws Exception
+	 */
+	// public String setOptionNum(String optionWordMl, String num) throws Exception
+	// {
+	// String tmpStr = DocxProcessUtil.BODY_HEADER + optionWordMl +
+	// DocxProcessUtil.BODY_TAIL;
+	// Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
+	// List<Object> pList = body.getContent();
+	// int index = 0;
+	// for (Object pObj : pList) {
+	// if (index > 0) {
+	// break;
+	// }
+	// P p = (P) pObj;
+	// List<Object> pContent = p.getContent();
+	// R run = new R();
+	// Text text = new Text();
+	// text.setValue(num + ". ");
+	// run.getContent().add(text);
+	// pContent.add(0, run);
+	// index++;
+	// }
+	// StringBuffer pWordMl = new StringBuffer();
+	// for (Object pObj : pList) {
+	// if (pObj instanceof P) {
+	// pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
+	// }
+	// }
+	// return pWordMl.toString();
+	// }
+
+	// public String setAnswerNum(String quesBodyWordMl) throws Exception {
+	// String tmpStr = DocxProcessUtil.BODY_HEADER + quesBodyWordMl +
+	// DocxProcessUtil.BODY_TAIL;
+	// Body body = (Body) XmlUtils.unmarshalString(tmpStr, Context.jc, Body.class);
+	// List<Object> pList = body.getContent();
+	// int index = 0;
+	// for (Object pObj : pList) {
+	// if (index > 0) {
+	// break;
+	// }
+	// P p = (P) pObj;
+	// List<Object> pContent = p.getContent();
+	// R run = new R();
+	// Text text = new Text();
+	// text.setValue("[答案]:");
+	// run.getContent().add(text);
+	// pContent.add(0, run);
+	// index++;
+	// }
+	// StringBuffer pWordMl = new StringBuffer();
+	// for (Object pObj : pList) {
+	// if (pObj instanceof P) {
+	// pWordMl.append(DocxProcessUtil.formatPWordMl(XmlUtils.marshaltoString(pObj)));
+	// }
+	// }
+	// return pWordMl.toString();
+	// }
+	@Override
+	public int getQuestionTypeNumbers(String paperId, Integer publicityType, Integer difficultyType) {
+		if (publicityType == null || difficultyType == null) {
+			return 0;
+		}
+		Boolean publicity = null;
+		String difficulty = null;
+		if (publicityType == 1) {
+			publicity = true;
+		} else {
+			publicity = false;
+		}
+		if (difficultyType == 1) {
+			difficulty = "易";
+		} else if (difficultyType == 2) {
+			difficulty = "中";
+		} else {
+			difficulty = "难";
+		}
+		String needType = publicity + "-" + difficulty;
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		List<PaperDetailUnit> units = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+		if (units != null && units.size() > 0) {
+			int total = 0;
+			for (PaperDetailUnit unit : units) {
+				if (unit.getQuestion().getPublicity() == null) {
+					unit.getQuestion().setPublicity(true);
+				}
+				if (unit.getQuestion().getDifficulty() == null) {
+					unit.getQuestion().setDifficulty("中");
+				}
+				String factType = unit.getQuestion().getPublicity() + "-" + unit.getQuestion().getDifficulty();
+				if (needType.equals(factType)) {
+					total += 1;
+				}
+			}
+			return total;
+		}
+		return 0;
+	}
 
-    }
+	@Override
+	public double getQuestionTypeScore(String paperId, Integer publicityType, Integer difficultyType) {
+		if (publicityType == null || difficultyType == null) {
+			return 0;
+		}
+		Boolean publicity = null;
+		String difficulty = null;
+		if (publicityType == 1) {
+			publicity = true;
+		} else {
+			publicity = false;
+		}
+		if (difficultyType == 1) {
+			difficulty = "易";
+		} else if (difficultyType == 2) {
+			difficulty = "中";
+		} else {
+			difficulty = "难";
+		}
+		String needType = publicity + "-" + difficulty;
+		Paper paper = Model.of(paperRepo.findById(paperId));
+		List<PaperDetailUnit> units = paperDetailUnitRepo.findByPaperOrderByNumber(paper);
+		if (units != null && units.size() > 0) {
+			double total = 0;
+			BigDecimal b1 = BigDecimal.valueOf(total);
+			for (PaperDetailUnit unit : units) {
+				if (unit.getQuestion().getPublicity() == null) {
+					unit.getQuestion().setPublicity(true);
+				}
+				if (unit.getQuestion().getDifficulty() == null) {
+					unit.getQuestion().setDifficulty("中");
+				}
+				String factType = unit.getQuestion().getPublicity() + "-" + unit.getQuestion().getDifficulty();
+				if (needType.equals(factType)) {
+					BigDecimal b2 = BigDecimal.valueOf(unit.getScore());
+					b1 = b1.add(b2);
+				}
+			}
+			return b1.doubleValue();
+		}
+		return 0;
+	}
 
-    private int getSubStringCount(String src, String find) {
-        int o = 0;
-        int index = -1;
-        while ((index = src.indexOf(find, index)) > -1) {
-            ++index;
-            ++o;
-        }
-        return o;
-    }
-
-    private void processSelectOption(Question question) {
-        String answer = question.getQuesAnswer();
-        if (StringUtils.isNotBlank(answer)) {
-            String[] answerArray = answer.split(",");
-            for (int i = 0; i < question.getQuesOptions().size(); i++) {
-                QuesOption quesOption = question.getQuesOptions().get(i);
-                char number = (char) (Integer.parseInt(quesOption.getNumber()) + 64);
-                if (ArrayUtils.contains(answerArray, String.valueOf(number))) {
-                    quesOption.setIsCorrect((short) 1);
-                } else {
-                    quesOption.setIsCorrect((short) 0);
-                }
-            }
-        }
-    }
+	private void clearPaperCache(String paperId) {
+		// 清理与当前试卷相关的缓存
+		// final String patternKey = CACHE_KEY_PAPER + "*" + paperId;
+		// Set<String> keys = redisTemplate.keys(patternKey);
+		// if (CollectionUtils.isNotEmpty(keys)) {
+		// redisTemplate.delete(keys);
+		// }
+		extractConfigPaperCache.remove(paperId);
+		basePaperCache.remove(paperId);
+	}
 
-    private void answerFomatErr(Integer subNum, Integer childNum, String msg) {
-        throw new StatusException("500", "导入数据中第" + subNum + "小题" + (childNum != null ? "第" + childNum + "子题" : "") + msg);
-    }
+	private void clearQuestionCache(String questionId) {
+		// 清理与当前试题相关的缓存
+		// final String patternKey = CACHE_KEY_QUESTION + "*" + questionId;
+		// Set<String> keys = redisTemplate.keys(patternKey);
+		// if (CollectionUtils.isNotEmpty(keys)) {
+		// redisTemplate.delete(keys);
+		// }
+		questionCache.remove(questionId);
+		questionAnswerCache.remove(questionId);
+	}
 
-    private String trimAndNullIfBlank(String s) {
-        if (StringUtils.isBlank(s)) {
-            return null;
-        }
-        return s.trim();
-    }
+	@Override
+	public List<PaperAnswerDomain> answerExport(Paper paper) {
+		List<PaperAnswerDomain> ret = new ArrayList<PaperAnswerDomain>();
+		List<PaperDetail> paperDetails = paperDetailRepo.findByPaperOrderByNumber(paper);
+		if (paperDetails != null && paperDetails.size() > 0) {
+			for (PaperDetail paperDetail : paperDetails) {
+				List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo
+						.findByPaperDetailOrderByNumber(paperDetail);
+				if (paperDetailUnits != null && paperDetailUnits.size() > 0) {
+					for (PaperDetailUnit paperDetailUnit : paperDetailUnits) {
+						Question ques = paperDetailUnit.getQuestion();
+						if (QuesStructType.NESTED_ANSWER_QUESTION.equals(ques.getQuestionType())) {
+							List<Question> subques = ques.getSubQuestions();
+							if (subques != null && subques.size() > 0) {
+								for (int i = 0; i < subques.size(); i++) {
+									Question subq = subques.get(i);
+									PaperAnswerDomain domain = new PaperAnswerDomain();
+									domain.setName(paperDetail.getName());
+									domain.setNumber(paperDetail.getNumber());
+									domain.setSubNumber(paperDetailUnit.getNumber());
+									domain.setSubType(subq.getQuestionType().getName());
+									domain.setChildNumber(i + 1);
+									domain.setAnswer(subq.getQuesAnswer());
+									ret.add(domain);
+								}
+							}
+						} else {
+							PaperAnswerDomain domain = new PaperAnswerDomain();
+							domain.setName(paperDetail.getName());
+							domain.setNumber(paperDetail.getNumber());
+							domain.setSubNumber(paperDetailUnit.getNumber());
+							domain.setSubType(paperDetailUnit.getQuestionType().getName());
+							domain.setAnswer(ques.getQuesAnswer());
+							ret.add(domain);
+						}
+					}
+				}
+			}
+		}
+		return ret;
+	}
 
-    private Integer getNumber(String s) {
-        if (StringUtils.isBlank(s)) {
-            return null;
-        }
-        return Integer.valueOf(s.trim());
-    }
+	@Override
+	public void answerImport(Paper paper, MultipartFile dataFile) {
+		File file = new File(systemProperties.getTempDataDir() + File.separator + UUID.randomUUID() + ".xlsx");
+		try {
+			file.createNewFile();
+			dataFile.transferTo(file);
+			List<String[]> lineList = getData(file);
+			if (lineList == null || lineList.size() == 0) {
+				throw new StatusException("500", "没有导入的数据");
+			}
+			List<PaperAnswerDomain> ret = new ArrayList<PaperAnswerDomain>();
+			for (int i = 0; i < lineList.size(); i++) {
+				String[] line = lineList.get(i);
+				PaperAnswerDomain domain = new PaperAnswerDomain();
+				domain.setNumber(getNumber(trimAndNullIfBlank(line[0])));
+				domain.setName(trimAndNullIfBlank(line[1]));
+				domain.setSubNumber(getNumber(trimAndNullIfBlank(line[2])));
+				domain.setChildNumber(getNumber(trimAndNullIfBlank(line[3])));
+				domain.setSubType(trimAndNullIfBlank(line[4]));
+				domain.setAnswer(trimAndNullIfBlank(line[5]));
+				ret.add(domain);
+			}
+			if (ret == null || ret.size() == 0) {
+				throw new StatusException("500", "没有导入的数据");
+			}
+			diposeAnswer(paper, ret);
+		} catch (StatusException e) {
+			throw e;
+		} catch (Exception e) {
+			throw new StatusException("500", "导入答案失败:" + e.getMessage(), e);
+		} finally {
+			FileUtil.deleteFile(file.getAbsolutePath());
+		}
+	}
+
+	private List<String[]> getData(File file) {
+		XSSFWorkbook wb = null;
+		try {
+			try {
+				wb = new XSSFWorkbook(file);
+			} catch (Exception e) {
+				throw new StatusException("500", "文件类型错误");
+			}
+			List<String[]> outerList = new ArrayList<String[]>();
+			XSSFSheet sheet = wb.getSheetAt(0);
+			for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+				String[] innerList = new String[6];
+				XSSFRow row = sheet.getRow(i);
+				for (int j = 0; j < 6; j++) {
+					XSSFCell cell = row.getCell(j);
+					if (cell != null) {
+						String cellinfo = cell.getStringCellValue();
+						innerList[j] = cellinfo;
+					} else {
+						innerList[j] = "";
+					}
+				}
+				if (!isEmpty(innerList)) {
+					if (StringUtils.isEmpty(innerList[0])) {
+						throw new StatusException("500", "文件类型错误" + (i + 1) + "行大题号不能为空");
+					}
+					if (StringUtils.isEmpty(innerList[2])) {
+						throw new StatusException("500", "文件类型错误" + (i + 1) + "行小题号不能为空");
+					}
+					outerList.add(innerList);
+				}
+			}
+			return outerList;
+		} finally {
+			if (wb != null) {
+				try {
+					wb.close();
+				} catch (IOException e) {
+					LOG.debug("wb.close() error " + e);
+				}
+			}
+		}
+
+	}
+
+	private boolean isEmpty(String[] ss) {
+		for (String s : ss) {
+			if (StringUtils.isNotEmpty(s)) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	private void diposeAnswer(Paper paper, List<PaperAnswerDomain> domains) {
+		Map<String, PaperAnswerDomain> answerMap = new LinkedHashMap<String, PaperAnswerDomain>();
+		for (PaperAnswerDomain domain : domains) {
+			String key = null;
+			if (domain.getChildNumber() != null) {
+				key = domain.getSubNumber() + "-" + domain.getChildNumber();
+			} else {
+				key = domain.getSubNumber().toString();
+			}
+			if (answerMap.get(key) == null) {
+				answerMap.put(key, domain);
+			} else {
+				throw new StatusException("500", "导入数据中第" + domain.getSubNumber() + "小题"
+						+ (domain.getChildNumber() != null ? "第" + domain.getChildNumber() + "子题" : "") + "重复");
+			}
+		}
+
+		Map<String, Question> quesMap = new LinkedHashMap<String, Question>();
+		List<PaperDetailUnit> paperDetailUnits = paperDetailUnitRepo.findByPaperIdOrderByNumber(paper.getId());
+		for (PaperDetailUnit unit : paperDetailUnits) {
+			Question qu = unit.getQuestion();
+			if (QuesStructType.NESTED_ANSWER_QUESTION.equals(qu.getQuestionType())) {
+				for (int i = 0; i < unit.getQuestion().getSubQuestions().size(); i++) {
+					quesMap.put(unit.getNumber() + "-" + (i + 1), unit.getQuestion().getSubQuestions().get(i));
+				}
+			} else {
+				quesMap.put(unit.getNumber().toString(), qu);
+			}
+		}
+
+		Set<String> saveQuestion = new HashSet<String>();
+		for (String key : answerMap.keySet()) {
+			PaperAnswerDomain domain = answerMap.get(key);
+			Question qu = quesMap.get(key);
+			if (qu == null) {
+				answerFomatErr(domain.getSubNumber(), domain.getChildNumber(), "试题信息不存在");
+			}
+			saveQuestion.add(domain.getSubNumber().toString());
+		}
+
+		Set<String> updateQuesIds = new HashSet<String>();
+		List<Question> ques = new ArrayList<Question>();
+		for (PaperDetailUnit unit : paperDetailUnits) {
+			Question qu = unit.getQuestion();
+			if (saveQuestion.contains(unit.getNumber().toString())) {
+				if (QuesStructType.NESTED_ANSWER_QUESTION.equals(qu.getQuestionType())) {
+					for (int i = 0; i < unit.getQuestion().getSubQuestions().size(); i++) {
+						if (answerMap.get(unit.getNumber() + "-" + (i + 1)) != null) {
+							checkAndSetAnswer(unit.getQuestion().getSubQuestions().get(i),
+									answerMap.get(unit.getNumber() + "-" + (i + 1)).getAnswer(), unit.getNumber(),
+									i + 1);
+							updateQuesIds.add(qu.getId());
+						}
+					}
+				} else {
+					if (answerMap.get(unit.getNumber().toString()) != null) {
+						checkAndSetAnswer(qu, answerMap.get(unit.getNumber().toString()).getAnswer(), unit.getNumber(),
+								null);
+						updateQuesIds.add(qu.getId());
+					}
+				}
+			}
+			ques.add(qu);
+		}
+		quesRepo.saveAll(ques);
+		// 清除缓存
+		for (String qid : updateQuesIds) {
+			paperDetailUnitService.clearQuestionCache(qid);
+		}
+	}
+
+	private void checkAndSetAnswer(Question question, String answer, Integer subNum, Integer childNum) {
+		String fomatErrMsg = "答案格式错误";
+		if (answer != null) {
+			answer = answer.trim();
+		}
+		if (StringUtils.isBlank(answer)) {
+			return;
+		}
+		if (QuesStructType.SINGLE_ANSWER_QUESTION.equals(question.getQuestionType())
+				|| QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(question.getQuestionType())) {
+			answer = answer.replaceAll("\\s*", "");
+			if (QuesStructType.SINGLE_ANSWER_QUESTION.equals(question.getQuestionType())) {
+				if (answer.length() > 1) {
+					answerFomatErr(subNum, childNum, fomatErrMsg);
+				}
+			}
+			String[] pAnswerArray;
+			if (answer.indexOf(",") != -1) {
+				pAnswerArray = answer.split(",");
+			} else {
+				pAnswerArray = answer.split("");
+			}
+			List<QuesOption> options = question.getQuesOptions();
+			List<String> optionNumList = new ArrayList<>();
+			for (QuesOption quesOption : options) {
+				Integer numInteger = Integer.parseInt(quesOption.getNumber());
+				char word = (char) (numInteger + 64);
+				optionNumList.add(word + "");
+			}
+			for (String option : pAnswerArray) {
+				String pattern = "[A-Z]";
+				if (!Pattern.matches(pattern, option)) {
+					answerFomatErr(subNum, childNum, fomatErrMsg);
+				}
+				if (!optionNumList.contains(option)) {
+					answerFomatErr(subNum, childNum, "答案选项不存在");
+				}
+			}
+			if (pAnswerArray.length > 1) {
+				question.setQuesAnswer(StringUtils.join(pAnswerArray, ","));
+			} else {
+				question.setQuesAnswer(answer);
+			}
+			processSelectOption(question);
+		} else if (QuesStructType.BOOL_ANSWER_QUESTION.equals(question.getQuestionType())) {
+			if (!answer.equals("正确") && !answer.equals("错误")) {
+				answerFomatErr(subNum, childNum, fomatErrMsg);
+			}
+			question.setQuesAnswer(answer);
+		} else if (QuesStructType.FILL_BLANK_QUESTION.equals(question.getQuestionType())) {
+			String body = question.getQuesBody();
+			Document bodyDoc = Jsoup.parse(body);
+			String bodyText = bodyDoc.body().html();
+			Document answerDoc = null;
+			try {
+				answerDoc = Jsoup.parse(answer);
+			} catch (Exception e) {
+				answerFomatErr(subNum, childNum, "答案格式html解析错误");
+			}
+			if (answerDoc.body().childrenSize() != 0) {
+				String answerText = answerDoc.body().html();
+				if (StringUtils.isNotBlank(answerText)) {
+					if (getSubStringCount(bodyText, "###") != answerText.split("##").length) {
+						answerFomatErr(subNum, childNum, "题干空格数量和答案数量不一致");
+					}
+				}
+			}
+			question.setQuesAnswer(answer);
+		} else if (QuesStructType.TEXT_ANSWER_QUESTION.equals(question.getQuestionType())) {
+			try {
+				Jsoup.parse(answer);
+			} catch (Exception e) {
+				answerFomatErr(subNum, childNum, "答案格式html解析错误");
+			}
+			question.setQuesAnswer(answer);
+		}
+
+	}
+
+	private int getSubStringCount(String src, String find) {
+		int o = 0;
+		int index = -1;
+		while ((index = src.indexOf(find, index)) > -1) {
+			++index;
+			++o;
+		}
+		return o;
+	}
+
+	private void processSelectOption(Question question) {
+		String answer = question.getQuesAnswer();
+		if (StringUtils.isNotBlank(answer)) {
+			String[] answerArray = answer.split(",");
+			for (int i = 0; i < question.getQuesOptions().size(); i++) {
+				QuesOption quesOption = question.getQuesOptions().get(i);
+				char number = (char) (Integer.parseInt(quesOption.getNumber()) + 64);
+				if (ArrayUtils.contains(answerArray, String.valueOf(number))) {
+					quesOption.setIsCorrect((short) 1);
+				} else {
+					quesOption.setIsCorrect((short) 0);
+				}
+			}
+		}
+	}
+
+	private void answerFomatErr(Integer subNum, Integer childNum, String msg) {
+		throw new StatusException("500",
+				"导入数据中第" + subNum + "小题" + (childNum != null ? "第" + childNum + "子题" : "") + msg);
+	}
+
+	private String trimAndNullIfBlank(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		return s.trim();
+	}
+
+	private Integer getNumber(String s) {
+		if (StringUtils.isBlank(s)) {
+			return null;
+		}
+		return Integer.valueOf(s.trim());
+	}
 
 	@Override
 	public List<String> findPaperId(Long fromRootOrgId) {
 		Query query = new Query();
 		query.addCriteria(Criteria.where("orgId").is(fromRootOrgId.toString()));
 		query.addCriteria(Criteria.where("paperType").is(PaperType.IMPORT));
-		List<PaperId> paperList = this.mongoTemplate.find(query, PaperId.class,"paper");
-		if(CollectionUtils.isEmpty(paperList)) {
+		List<PaperId> paperList = this.mongoTemplate.find(query, PaperId.class, "paper");
+		if (CollectionUtils.isEmpty(paperList)) {
 			return null;
 		}
-		List<String> ids=paperList.stream().map(e->e.getId()).collect(Collectors.toList());
+		List<String> ids = paperList.stream().map(e -> e.getId()).collect(Collectors.toList());
 		return ids;
 	}
 
-    
-
 }

+ 17 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/QuestionAudioServiceImpl.java

@@ -202,6 +202,23 @@ public class QuestionAudioServiceImpl implements QuestionAudioService {
         while (audioIterator.hasNext()) {
             QuestionAudio audio = audioIterator.next();
 //            UpYun upYun = new UpYun(sysProperty.getBucketName(), sysProperty.getUserName(), sysProperty.getPassword());
+//            upYun.deleteFile(audio.getFileUrl());
+        	//通用存储
+            FileStorageUtil.deleteFile(audio.getFileUrl());
+        }
+        questionAudioRepo.deleteAll(questionAudios);
+    }
+    
+    @Override
+    public void deleteAudioByQuestionId(List<String> questionIds) {
+    	if(CollectionUtils.isEmpty(questionIds)) {
+    		return;
+    	}
+        List<QuestionAudio> questionAudios = questionAudioRepo.findByQuestionIdIn(questionIds);
+        Iterator<QuestionAudio> audioIterator = questionAudios.iterator();
+        while (audioIterator.hasNext()) {
+            QuestionAudio audio = audioIterator.next();
+//            UpYun upYun = new UpYun(sysProperty.getBucketName(), sysProperty.getUserName(), sysProperty.getPassword());
 //            upYun.deleteFile(audio.getFileUrl());
         	//通用存储
             FileStorageUtil.deleteFile(audio.getFileUrl());

+ 35 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/RandomPaperQuestionServiceImpl.java

@@ -0,0 +1,35 @@
+package cn.com.qmth.examcloud.core.questions.service.impl;
+
+import java.util.List;
+
+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.stereotype.Service;
+
+import cn.com.qmth.examcloud.core.questions.dao.entity.RandomPaperQuestion;
+import cn.com.qmth.examcloud.core.questions.service.RandomPaperQuestionService;
+
+@Service
+public class RandomPaperQuestionServiceImpl implements RandomPaperQuestionService {
+	@Autowired
+	private MongoTemplate mongoTemplate;
+	
+	@Override
+	public boolean existQuestion(String questionId) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("questionId").is(questionId));
+		RandomPaperQuestion rp=mongoTemplate.findOne(query,RandomPaperQuestion.class);
+		return rp!=null;
+	}
+	
+	@Override
+	public boolean existQuestion(List<String> questionIds) {
+		Query query = new Query();
+		query.addCriteria(Criteria.where("questionId").in(questionIds));
+		long count=mongoTemplate.count(query,RandomPaperQuestion.class);
+		return count>0;
+	}
+
+}

+ 1208 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/RandomPaperServiceImpl.java

@@ -0,0 +1,1208 @@
+package cn.com.qmth.examcloud.core.questions.service.impl;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.basic.api.UserCloudService;
+import cn.com.qmth.examcloud.core.basic.api.bean.UserBean;
+import cn.com.qmth.examcloud.core.basic.api.request.GetUserListByIdsReq;
+import cn.com.qmth.examcloud.core.basic.api.request.GetUserReq;
+import cn.com.qmth.examcloud.core.basic.api.response.GetUserListByIdsResp;
+import cn.com.qmth.examcloud.core.basic.api.response.GetUserResp;
+import cn.com.qmth.examcloud.core.questions.base.CommonUtils;
+import cn.com.qmth.examcloud.core.questions.base.Model;
+import cn.com.qmth.examcloud.core.questions.base.enums.PaperStructType;
+import cn.com.qmth.examcloud.core.questions.base.enums.QuestionDifficulty;
+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.PropertyRepo;
+import cn.com.qmth.examcloud.core.questions.dao.RandomPaperQuestionRepo;
+import cn.com.qmth.examcloud.core.questions.dao.RandomPaperRepo;
+import cn.com.qmth.examcloud.core.questions.dao.entity.*;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.CoursePropertyNumberDto;
+import cn.com.qmth.examcloud.core.questions.dao.entity.dto.PaperDetailUnitStructDto;
+import cn.com.qmth.examcloud.core.questions.service.PaperStructService;
+import cn.com.qmth.examcloud.core.questions.service.RandomPaperService;
+import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.*;
+import cn.com.qmth.examcloud.core.questions.service.cache.PaperDetailStructCache;
+import cn.com.qmth.examcloud.core.questions.service.cache.PaperStructCache;
+import cn.com.qmth.examcloud.core.questions.service.cache.RandomPaperCache;
+import cn.com.qmth.examcloud.core.questions.service.util.BatchGetDataUtil;
+import cn.com.qmth.examcloud.core.questions.service.util.BatchSetDataUtil;
+import cn.com.qmth.examcloud.core.questions.service.util.PaperUtil;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultPaper;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionGroup;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionStructureWrapper;
+import cn.com.qmth.examcloud.question.commons.core.paper.DefaultQuestionUnitWrapper;
+import cn.com.qmth.examcloud.question.commons.core.question.QuestionType;
+import cn.com.qmth.examcloud.support.CacheConstants;
+import cn.com.qmth.examcloud.support.cache.CacheHelper;
+import cn.com.qmth.examcloud.support.cache.bean.CourseCacheBean;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageImpl;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
+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.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Service
+public class RandomPaperServiceImpl implements RandomPaperService {
+
+    private static final Logger log = LoggerFactory.getLogger(RandomPaperServiceImpl.class);
+
+    private static Cache<String, RandomPaperCache> localRandomPaperCache = CacheBuilder.newBuilder().expireAfterWrite(3, TimeUnit.MINUTES).build();
+
+    private static int cacheTimeOut = 2 * 60 * 60;
+
+    @Autowired
+    private MongoTemplate mongoTemplate;
+
+    @Resource(name = "mongoTemplate2")
+    private MongoTemplate mongoTemplate2;
+
+    @Autowired
+    private PaperStructService paperStructService;
+
+    @Autowired
+    private PaperStructRepo paperStructRepo;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    @Autowired
+    private PropertyRepo propertyRepo;
+
+    @Autowired
+    private RandomPaperRepo randomPaperRepo;
+
+    @Autowired
+    private RandomPaperQuestionRepo randomPaperQuestionRepo;
+
+    @Autowired
+    private UserCloudService userCloudService;
+
+    @Override
+    public Page<RandomPaperListVo> getPage(RandomPaperQuery req) {
+        if (req.getUd().assertEmptyQueryResult()) {
+            return Page.empty();
+        }
+        Query query;
+        List<Criteria> cs = new ArrayList<>();
+        cs.add(Criteria.where("rootOrgId").is(req.getRootOrgId()));
+
+        if (req.getUd().assertNeedQueryRefIds()) {
+            cs.add(Criteria.where("courseId").in(req.getUd().getRefIds()));
+        }
+
+        if (req.getEnable() != null) {
+            cs.add(Criteria.where("enable").is(req.getEnable()));
+        }
+
+        if (req.getCourseId() != null) {
+            cs.add(Criteria.where("courseId").is(req.getCourseId()));
+        }
+        if (StringUtils.isNotBlank(req.getName())) {
+            String paperName = CommonUtils.escapeExprSpecialWord(req.getName());
+            cs.add(Criteria.where("name").regex(".*?\\.*" + paperName + ".*"));
+        }
+        Criteria and = new Criteria();
+        Criteria[] cas = new Criteria[cs.size()];
+        if (StringUtils.isNotBlank(req.getId())) {
+            and.andOperator(cs.toArray(cas));
+            query = Query.query(new Criteria().orOperator(and, Criteria.where("id").is(req.getId())));
+        } else {
+            and.andOperator(cs.toArray(cas));
+            query = Query.query(and);
+        }
+
+        long total = this.mongoTemplate.count(query, RandomPaper.class);
+        if (total == 0) {
+            return Page.empty();
+        }
+
+        PageRequest pageable = PageRequest.of(req.getPageNumber() - 1, req.getPageSize());
+        query.with(Sort.by(Sort.Order.desc("creationDate")));
+        query.skip(pageable.getOffset());
+        query.limit(pageable.getPageSize());
+
+        List<RandomPaperListVo> paperList = this.mongoTemplate.find(query, RandomPaperListVo.class, "randomPaper");
+        if (CollectionUtils.isEmpty(paperList)) {
+            return Page.empty();
+        }
+
+        for (RandomPaperListVo vo : paperList) {
+            CourseCacheBean course = CacheHelper.getCourse(vo.getCourseId());
+            vo.setCourseCode(course.getCode());
+            vo.setCourseName(course.getName());
+            vo.setPaperStructTypeStr(vo.getPaperStructType().getName());
+            PaperStruct paperStruct = Model.of(paperStructRepo.findById(vo.getPaperStructId()));
+            vo.setPaperStructName(paperStruct.getName());
+            vo.setEnableStr(vo.getEnable() ? "启用" : "禁用");
+        }
+        fillUserName(paperList, req.getRootOrgId());
+
+        return new PageImpl<>(paperList, pageable, total);
+    }
+
+    private void fillUserName(List<RandomPaperListVo> dtos, Long rootOrgId) {
+        if (dtos != null && dtos.size() > 0) {
+            List<Long> ids = dtos.stream().map(dto -> dto.getUpdateBy()).distinct().collect(Collectors.toList());
+            List<UserBean> userList = new ArrayList<UserBean>();
+            GetUserListByIdsReq req = new GetUserListByIdsReq();
+            BatchGetDataUtil<UserBean, Long> tool = new BatchGetDataUtil<UserBean, Long>() {
+                @Override
+                public List<UserBean> getData(List<Long> paramList) {
+                    req.setRootOrgId(rootOrgId);
+                    req.setUserIdList(paramList);
+                    GetUserListByIdsResp resp = userCloudService.getUserListByIds(req);
+                    return resp.getUserBeanList();
+                }
+
+            };
+            tool.getDataForBatch(userList, ids, 100);
+            Map<Long, UserBean> map = userList.stream()
+                    .collect(Collectors.toMap(UserBean::getUserId, account -> account, (key1, key2) -> key2));
+            for (RandomPaperListVo markerBean : dtos) {
+                UserBean userBean = map.get(markerBean.getUpdateBy());
+                markerBean.setUpdateByName(userBean.getDisplayName());
+            }
+        }
+    }
+
+    @Transactional
+    @Override
+    public void toggle(String id, Boolean enable, User user) {
+        RandomPaper paperStruct = Model.of(randomPaperRepo.findById(id));
+        if (paperStruct == null) {
+            throw new StatusException("未找到模板");
+        }
+        if (!paperStruct.getRootOrgId().equals(user.getRootOrgId())) {
+            throw new StatusException("非法操作");
+        }
+        paperStruct.setEnable(enable);
+        randomPaperRepo.save(paperStruct);
+    }
+
+    @Override
+    public StructInfo getStructQuestionInfo(String structId) {
+        StructInfo ret = new StructInfo();
+        PaperStruct ps = Model.of(paperStructRepo.findById(structId));
+        ret.setTotalScore(ps.getTotalScore());
+        if (PaperStructType.BLUEPRINT.equals(ps.getPaperStrucType())) {
+            ret.setDifficultyDegree(ps.getDifficulty());
+            if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+                List<StructQuestionInfo> sqinfos = new ArrayList<>();
+                ret.setStructQuestionInfo(sqinfos);
+                for (PaperDetailStruct paperDetailStruct : ps.getPaperDetailStructs()) {
+                    StructQuestionInfo sqinfo = new StructQuestionInfo();
+                    sqinfos.add(sqinfo);
+                    sqinfo.setDetailName(paperDetailStruct.getName());
+                    sqinfo.setTotalCount(paperDetailStruct.getDetailCount());
+                    sqinfo.setTotalScore(paperDetailStruct.getTotalScore());
+                    Integer simpleCount = 0;
+                    Integer mediumCount = 0;
+                    Integer difficultyCount = 0;
+                    simpleCount = paperDetailStruct.getPublicSimpleCount() + paperDetailStruct.getNoPublicSimpleCount();
+                    mediumCount = paperDetailStruct.getPublicMediumCount() + paperDetailStruct.getNoPublicMediumCount();
+                    difficultyCount = paperDetailStruct.getPublicDifficultyCount()
+                            + paperDetailStruct.getNoPublicDifficultyCount();
+                    sqinfo.setHardInfo(new StructQuestionCountInfo(difficultyCount, true));
+                    sqinfo.setMediumInfo(new StructQuestionCountInfo(mediumCount, true));
+                    sqinfo.setEasyInfo(new StructQuestionCountInfo(simpleCount, true));
+                }
+            }
+        } else if (PaperStructType.EXACT.equals(ps.getPaperStrucType())) {
+            ret.setDifficultyDegree(getExactDifficulty(ps));
+            if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+                List<StructQuestionInfo> sqinfos = new ArrayList<>();
+                ret.setStructQuestionInfo(sqinfos);
+                for (PaperDetailStruct paperDetailStruct : ps.getPaperDetailStructs()) {
+                    StructQuestionInfo sqinfo = new StructQuestionInfo();
+                    sqinfos.add(sqinfo);
+                    sqinfo.setDetailName(paperDetailStruct.getName());
+                    sqinfo.setTotalCount(paperDetailStruct.getDetailCount());
+                    sqinfo.setTotalScore(paperDetailStruct.getTotalScore());
+                    Integer simpleCount = 0;
+                    Integer mediumCount = 0;
+                    Integer difficultyCount = 0;
+                    if (CollectionUtils.isNotEmpty(paperDetailStruct.getUnitStructs())) {
+                        for (PaperDetailUnitStructDto unitStruct : paperDetailStruct.getUnitStructs()) {
+                            simpleCount = simpleCount + unitStruct.getPublicSimple() + unitStruct.getNoPublicSimple();
+                            mediumCount = mediumCount + unitStruct.getPublicMedium() + unitStruct.getNoPublicMedium();
+                            difficultyCount = difficultyCount + unitStruct.getPublicDifficulty()
+                                    + unitStruct.getNoPublicDifficulty();
+                        }
+                    }
+                    sqinfo.setHardInfo(new StructQuestionCountInfo(difficultyCount, true));
+                    sqinfo.setMediumInfo(new StructQuestionCountInfo(mediumCount, true));
+                    sqinfo.setEasyInfo(new StructQuestionCountInfo(simpleCount, true));
+                }
+            }
+        }
+        return ret;
+    }
+
+    private Double getExactDifficulty(PaperStruct ps) {
+        Double sum = 0.0;
+        Double totalScore = ps.getTotalScore();
+        if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+            Integer simpleCount = 0;
+            Integer mediumCount = 0;
+            Integer difficultyCount = 0;
+            for (PaperDetailStruct paperDetailStruct : ps.getPaperDetailStructs()) {
+                if (CollectionUtils.isNotEmpty(paperDetailStruct.getUnitStructs())) {
+                    for (PaperDetailUnitStructDto unitStruct : paperDetailStruct.getUnitStructs()) {
+                        simpleCount = unitStruct.getPublicSimple() + unitStruct.getNoPublicSimple();
+                        mediumCount = unitStruct.getPublicMedium() + unitStruct.getNoPublicMedium();
+                        difficultyCount = unitStruct.getPublicDifficulty() + unitStruct.getNoPublicDifficulty();
+                        sum = simpleCount * unitStruct.getScore() * 0.8 + mediumCount * unitStruct.getScore() * 0.5
+                                + difficultyCount * unitStruct.getScore() * 0.2 + sum;
+                    }
+                }
+            }
+
+            Double dif = sum / totalScore;
+            BigDecimal b = BigDecimal.valueOf(dif);
+            Double difficulty = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
+            return difficulty;
+        }
+        return (double) 0;
+    }
+
+    @Override
+    public StructInfo getPaperQuestionViewInfo(PaperQuestionViewQuery query) {
+        String structId = query.getStructId();
+        List<String> paperIds = query.getPaperIds();
+        if (StringUtils.isBlank(structId)) {
+            throw new StatusException("structId不能为空");
+        }
+        if (CollectionUtils.isEmpty(paperIds)) {
+            throw new StatusException("paperIds不能为空");
+        }
+        StructInfo ret = getPaperQuestionInfo(structId, paperIds);
+        clearQuestionIds(ret);
+        return ret;
+    }
+
+    private StructInfo getPaperQuestionInfo(String structId, List<String> paperIds) {
+        StructInfo ret = new StructInfo();
+        ret.setValid(true);
+        PaperStruct ps = Model.of(paperStructRepo.findById(structId));
+        List<QuestionDto> questionList = new ArrayList<>();
+        List<PaperDetailUnitDto> unitList = findUnitByPaperIds(paperIds);
+        fillQuestionAndDetail(unitList);
+        StructQuestionCheckDto cd = new StructQuestionCheckDto();
+        cd.setQuestionList(questionList);
+        if (PaperStructType.BLUEPRINT.equals(ps.getPaperStrucType())) {
+            for (PaperDetailUnitDto unit : unitList) {
+                unit.getQuestion().setQuesName(unit.getPaperDetail().getName());
+                unit.getQuestion().setPropertyGroup(bulidPropertyGroup(unit.getQuestion()));
+                questionList.add(unit.getQuestion());
+            }
+            if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+                List<StructQuestionInfo> sqinfos = new ArrayList<>();
+                ret.setStructQuestionInfo(sqinfos);
+                int detailNumber = 0;
+                for (PaperDetailStruct ds : ps.getPaperDetailStructs()) {
+                    detailNumber++;
+                    StructQuestionInfo sqinfo = new StructQuestionInfo();
+                    sqinfos.add(sqinfo);
+                    sqinfo.setDetailName(ds.getName());
+                    cd.setDetailNumber(detailNumber);
+                    cd.setDs(ds);
+                    cd.setSqinfo(sqinfo);
+                    cd.setUnitScore(ds.getScore());
+                    for (CoursePropertyNumberDto cp : ds.getCoursePropertyNumberDtos()) {
+                        if (!cp.getDisable()) {
+                            cd.setCp(cp);
+                            setQuestionInfoByBlue(cd);
+                        }
+                    }
+                    sqinfo.setTotalCount(sqinfo.getHardInfo().getCount() + sqinfo.getMediumInfo().getCount()
+                            + sqinfo.getEasyInfo().getCount());
+                }
+            }
+        } else if (PaperStructType.EXACT.equals(ps.getPaperStrucType())) {
+            for (PaperDetailUnitDto unit : unitList) {
+                unit.getQuestion().setQuesName(unit.getPaperDetail().getName());
+                questionList.add(unit.getQuestion());
+            }
+            if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+                List<StructQuestionInfo> sqinfos = new ArrayList<>();
+                ret.setStructQuestionInfo(sqinfos);
+                int detailNumber = 0;
+                for (PaperDetailStruct ds : ps.getPaperDetailStructs()) {
+                    detailNumber++;
+                    StructQuestionInfo sqinfo = new StructQuestionInfo();
+                    sqinfos.add(sqinfo);
+                    sqinfo.setDetailName(ds.getName());
+                    cd.setDetailNumber(detailNumber);
+                    cd.setSqinfo(sqinfo);
+                    int index = 0;
+                    for (PaperDetailUnitStructDto us : ds.getUnitStructs()) {
+                        index++;
+                        cd.setUnitScore(us.getScore());
+                        cd.setIndex(index);
+                        cd.setUs(us);
+                        setQuestionInfoByExact(cd);
+                    }
+                    sqinfo.setTotalCount(sqinfo.getHardInfo().getCount() + sqinfo.getMediumInfo().getCount()
+                            + sqinfo.getEasyInfo().getCount());
+                }
+            }
+        }
+        fillValid(ret);
+        return ret;
+    }
+
+    private void fillQuestionAndDetail(List<PaperDetailUnitDto> units) {
+        if (CollectionUtils.isNotEmpty(units)) {
+            new BatchSetDataUtil<PaperDetailUnitDto>() {
+
+                @Override
+                protected void setData(List<PaperDetailUnitDto> dataList) {
+                    List<String> ids = dataList.stream().map(p -> p.getQuestion().getId()).collect(Collectors.toList());
+                    List<QuestionDto> temList = findQuestionByIds(ids);
+                    if (CollectionUtils.isNotEmpty(temList)) {
+                        Map<String, QuestionDto> map = new HashMap<>();
+                        for (QuestionDto vo : temList) {
+                            map.put(vo.getId(), vo);
+                        }
+                        for (PaperDetailUnitDto dto : dataList) {
+                            dto.setQuestion(map.get(dto.getQuestion().getId()));
+                        }
+                    }
+                    ids = dataList.stream().map(p -> p.getPaperDetail().getId()).collect(Collectors.toList());
+                    List<PaperDetailDto> details = findDetailByIds(ids);
+                    if (CollectionUtils.isNotEmpty(temList)) {
+                        Map<String, PaperDetailDto> map = new HashMap<>();
+                        for (PaperDetailDto vo : details) {
+                            map.put(vo.getId(), vo);
+                        }
+                        for (PaperDetailUnitDto dto : dataList) {
+                            dto.setPaperDetail(map.get(dto.getPaperDetail().getId()));
+                        }
+                    }
+                }
+            }.setDataForBatch(units, 1000);
+        }
+    }
+
+    private List<QuestionDto> findQuestionByIds(List<String> questionIds) {
+        List<Object> ids = new ArrayList<>();
+        for (String pid : questionIds) {
+            if (pid.length() > 24) {
+                ids.add(pid);
+            } else {
+                ids.add(new ObjectId(pid));
+            }
+        }
+        Query query = new Query();
+        query.addCriteria(Criteria.where("id").in(ids));
+        List<QuestionDto> units = this.mongoTemplate.find(query, QuestionDto.class, "question");
+        return units;
+    }
+
+    private List<PaperDetailDto> findDetailByIds(List<String> detailIds) {
+        List<Object> ids = new ArrayList<>();
+        for (String pid : detailIds) {
+            if (pid.length() > 24) {
+                ids.add(pid);
+            } else {
+                ids.add(new ObjectId(pid));
+            }
+        }
+        Query query = new Query();
+        query.addCriteria(Criteria.where("id").in(ids));
+        List<PaperDetailDto> units = this.mongoTemplate.find(query, PaperDetailDto.class, "paperDetail");
+        return units;
+    }
+
+    private void fillValid(StructInfo ret) {
+        if (CollectionUtils.isEmpty(ret.getStructQuestionInfo())) {
+            ret.setValid(false);
+            return;
+        }
+        for (StructQuestionInfo si : ret.getStructQuestionInfo()) {
+            if (!si.getHardInfo().getValid()) {
+                ret.setValid(false);
+            }
+            if (!si.getMediumInfo().getValid()) {
+                ret.setValid(false);
+            }
+            if (!si.getEasyInfo().getValid()) {
+                ret.setValid(false);
+            }
+        }
+    }
+
+    private void clearQuestionIds(StructInfo ret) {
+        if (CollectionUtils.isEmpty(ret.getStructQuestionInfo())) {
+            ret.setValid(false);
+            return;
+        }
+        for (StructQuestionInfo si : ret.getStructQuestionInfo()) {
+            if (!si.getHardInfo().getValid()) {
+                ret.setValid(false);
+            }
+            if (!si.getMediumInfo().getValid()) {
+                ret.setValid(false);
+            }
+            if (!si.getEasyInfo().getValid()) {
+                ret.setValid(false);
+            }
+            for (RandomPaperQuestionDto dto : si.getHardInfo().getQuestionInfo()) {
+                dto.setQuestionDtos(new ArrayList<>());
+            }
+            for (RandomPaperQuestionDto dto : si.getMediumInfo().getQuestionInfo()) {
+                dto.setQuestionDtos(new ArrayList<>());
+            }
+            for (RandomPaperQuestionDto dto : si.getEasyInfo().getQuestionInfo()) {
+                dto.setQuestionDtos(new ArrayList<>());
+            }
+        }
+    }
+
+    private void setQuestionInfoByExact(StructQuestionCheckDto cd) {
+        PaperDetailUnitStructDto us = cd.getUs();
+        StructQuestionInfo sqinfo = cd.getSqinfo();
+        if (us.getNoPublicDifficulty() > 0) {
+            cd.setSi(sqinfo.getHardInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.HARD.getName());
+            cd.setNeedCount(us.getNoPublicDifficulty());
+            setQuestionInfoByExactItem(cd);
+        }
+        if (us.getPublicDifficulty() > 0) {
+            cd.setSi(sqinfo.getHardInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.HARD.getName());
+            cd.setNeedCount(us.getPublicDifficulty());
+            setQuestionInfoByExactItem(cd);
+        }
+
+        if (us.getNoPublicMedium() > 0) {
+            cd.setSi(sqinfo.getMediumInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
+            cd.setNeedCount(us.getNoPublicMedium());
+            setQuestionInfoByExactItem(cd);
+        }
+        if (us.getPublicMedium() > 0) {
+            cd.setSi(sqinfo.getMediumInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
+            cd.setNeedCount(us.getPublicMedium());
+            setQuestionInfoByExactItem(cd);
+        }
+        if (us.getNoPublicSimple() > 0) {
+            cd.setSi(sqinfo.getEasyInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.EASY.getName());
+            cd.setNeedCount(us.getNoPublicSimple());
+            setQuestionInfoByExactItem(cd);
+        }
+        if (us.getPublicSimple() > 0) {
+            cd.setSi(sqinfo.getEasyInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.EASY.getName());
+            cd.setNeedCount(us.getPublicSimple());
+            setQuestionInfoByExactItem(cd);
+        }
+    }
+
+    private void setQuestionInfoByExactItem(StructQuestionCheckDto cd) {
+        StructQuestionCountInfo si = cd.getSi();
+        RandomPaperQuestionDto rq = new RandomPaperQuestionDto();
+        rq.setUnitScore(cd.getUnitScore());
+        rq.setDetailNumber(cd.getDetailNumber());
+        rq.setKey(cd.getIndex() + "-" + cd.getPub() + "-" + cd.getDifficulty());
+        si.getQuestionInfo().add(rq);
+        if (CollectionUtils.isNotEmpty(cd.getQuestionList())) {
+            Iterator<QuestionDto> it = cd.getQuestionList().iterator();
+            while (it.hasNext()) {
+                QuestionDto q = it.next();
+                if (cd.getUsedQuesIds().contains(q.getId())) {
+                    it.remove();
+                } else {
+                    if (checkExactQuesType(cd.getUs().getQuesNames(), cd.getUs().getQuestionType(), cd.getPub(),
+                            cd.getDifficulty(), q)) {
+                        rq.getQuestionDtos().add(q);
+                        cd.getUsedQuesIds().add(q.getId());
+                        it.remove();
+                    }
+                }
+            }
+        }
+        si.setCount(si.getCount() + rq.getQuestionDtos().size());
+        if (si.getValid() && cd.getNeedCount() > rq.getQuestionDtos().size()) {
+            si.setValid(false);
+            si.setInvalidMsg(getExactErrmsg(cd.getIndex(), cd.getDetailNumber(), cd.getPub(), cd.getDifficulty()));
+        }
+    }
+
+    private void setQuestionInfoByBlue(StructQuestionCheckDto cd) {
+        StructQuestionInfo sqinfo = cd.getSqinfo();
+        CoursePropertyNumberDto cp = cd.getCp();
+        if (cp.getNoPublicDifficulty() > 0) {
+            cd.setSi(sqinfo.getHardInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.HARD.getName());
+            cd.setNeedCount(cp.getNoPublicDifficulty());
+            setQuestionInfoByBlueProp(cd);
+        }
+        if (cp.getPublicDifficulty() > 0) {
+            cd.setSi(sqinfo.getHardInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.HARD.getName());
+            cd.setNeedCount(cp.getPublicDifficulty());
+            setQuestionInfoByBlueProp(cd);
+        }
+
+        if (cp.getNoPublicMedium() > 0) {
+            cd.setSi(sqinfo.getMediumInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
+            cd.setNeedCount(cp.getNoPublicMedium());
+            setQuestionInfoByBlueProp(cd);
+        }
+        if (cp.getPublicMedium() > 0) {
+            cd.setSi(sqinfo.getMediumInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.MEDIUM.getName());
+            cd.setNeedCount(cp.getPublicMedium());
+            setQuestionInfoByBlueProp(cd);
+        }
+        if (cp.getNoPublicSimple() > 0) {
+            cd.setSi(sqinfo.getEasyInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.EASY.getName());
+            cd.setNeedCount(cp.getNoPublicSimple());
+            setQuestionInfoByBlueProp(cd);
+        }
+        if (cp.getPublicSimple() > 0) {
+            cd.setSi(sqinfo.getEasyInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.EASY.getName());
+            cd.setNeedCount(cp.getPublicSimple());
+            setQuestionInfoByBlueProp(cd);
+        }
+    }
+
+    private void setQuestionInfoByBlueProp(StructQuestionCheckDto cd) {
+        StructQuestionCountInfo si = cd.getSi();
+        RandomPaperQuestionDto rq = new RandomPaperQuestionDto();
+        rq.setUnitScore(cd.getUnitScore());
+        rq.setDetailNumber(cd.getDetailNumber());
+        rq.setKey(bulidPropertyGroupByBlueStruct(cd.getCp().getPropertyParentId(), cd.getCp().getPropertyId(),
+                cd.getPub(), cd.getDifficulty()));
+        si.getQuestionInfo().add(rq);
+        if (CollectionUtils.isNotEmpty(cd.getQuestionList())) {
+            Iterator<QuestionDto> it = cd.getQuestionList().iterator();
+            while (it.hasNext()) {
+                QuestionDto q = it.next();
+                if (cd.getUsedQuesIds().contains(q.getId())) {
+                    it.remove();
+                } else {
+                    if (checkBlueQuesType(cd.getDs().getQuesNames(), cd.getDs().getQuestionType(), rq.getKey(), q)) {
+                        rq.getQuestionDtos().add(q);
+                        cd.getUsedQuesIds().add(q.getId());
+                        it.remove();
+                    }
+                }
+            }
+        }
+        si.setCount(si.getCount() + rq.getQuestionDtos().size());
+        if (si.getValid() && cd.getNeedCount() > rq.getQuestionDtos().size()) {
+            si.setValid(false);
+            si.setInvalidMsg(getBlueErrmsg(cd.getDetailNumber(), cd.getCp().getPropertyParentId(),
+                    cd.getCp().getPropertyId(), cd.getPub(), cd.getDifficulty()));
+        }
+    }
+
+    private String getExactErrmsg(Integer index, Integer detailNumber, Boolean pub, String difficulty) {
+        String pubstr;
+        if (pub) {
+            pubstr = "公开";
+        } else {
+            pubstr = "非公开";
+        }
+        return "第" + detailNumber + "大题 " + "第" + index + "题型结构 " + pubstr + "-" + difficulty + "题源数量不满足";
+    }
+
+    private String getBlueErrmsg(Integer detailNumber, String pproid, String proid, Boolean pub, String difficulty) {
+        String pubstr;
+        if (pub) {
+            pubstr = "公开";
+        } else {
+            pubstr = "非公开";
+        }
+        if (StringUtils.isNotBlank(pproid) && !"0".equals(pproid)) {
+            // 有一级 和 二级
+            Property fp = Model.of(propertyRepo.findById(pproid));
+            Property sp = Model.of(propertyRepo.findById(proid));
+            return "第" + detailNumber + "大题 " + fp.getName() + "-" + sp.getName() + "-" + pubstr + "-" + difficulty
+                    + "题源数量不满足";
+        } else {
+            // 有一级 无 二级
+            Property fp = Model.of(propertyRepo.findById(proid));
+            return "第" + detailNumber + "大题 " + fp.getName() + "-" + pubstr + "-" + difficulty + "题源数量不满足";
+        }
+    }
+
+    private boolean checkExactQuesType(List<String> quesNames, QuesStructType st, Boolean pub, String difficulty,
+                                       QuestionDto question) {
+        if (CollectionUtils.isNotEmpty(quesNames)) {
+            if (quesNames.contains(question.getQuesName()) && st.equals(question.getQuestionType())) {
+                if (question.getPublicity().equals(pub) && question.getDifficulty().equals(difficulty)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private boolean checkBlueQuesType(List<String> quesNames, QuesStructType st, String propertyGroup,
+                                      QuestionDto question) {
+        if (CollectionUtils.isNotEmpty(quesNames)) {
+            if (quesNames.contains(question.getQuesName()) && st.equals(question.getQuestionType())) {
+                if (question.getPropertyGroup() != null) {
+                    if (question.getPropertyGroup().contains(propertyGroup)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private List<PaperDetailUnitDto> findUnitByPaperIds(List<String> paperIds) {
+        List<Object> ids = new ArrayList<>();
+        for (String pid : paperIds) {
+            if (pid.length() > 24) {
+                ids.add(pid);
+            } else {
+                ids.add(new ObjectId(pid));
+            }
+        }
+        Query query = new Query();
+        query.addCriteria(Criteria.where("paper.$id").in(ids));
+        List<PaperDetailUnitDto> units = this.mongoTemplate2.find(query, PaperDetailUnitDto.class, "paperDetailUnit");
+        return units;
+    }
+
+    private String bulidPropertyGroupByBlueStruct(String pproid, String proid, Boolean pub, String difficulty) {
+        String propertyGroup = null;
+        // 获取试题关联的多组属性
+        if (StringUtils.isNotBlank(pproid) && !"0".equals(pproid)) {
+            // 有一级 和 二级
+            propertyGroup = pproid + "-" + proid + "-" + pub + "-" + difficulty;
+        } else {
+            // 有一级 无 二级
+            propertyGroup = proid + "-" + pub + "-" + difficulty;
+        }
+        return propertyGroup;
+    }
+
+    private List<String> bulidPropertyGroup(QuestionDto question) {
+        String propertyGroup = null;
+        List<String> propertyGroups = new ArrayList<>();
+        // 获取试题关联的多组属性
+        List<QuesProperty> quesProperties = question.getQuesProperties();
+        if (quesProperties != null && quesProperties.size() > 0) {
+            for (QuesProperty quesProperty : quesProperties) {
+                if (quesProperty.getSecondProperty() != null) {
+                    // 有一级 和 二级
+                    if (quesProperty.getSecondProperty().getId() == null
+                            || StringUtils.isBlank(String.valueOf(quesProperty.getSecondProperty().getId()))) {
+                        propertyGroup = String.valueOf(quesProperty.getFirstProperty().getId()) + "-"
+                                + String.valueOf(question.getPublicity()) + "-" + question.getDifficulty();
+                    } else {
+                        propertyGroup = String.valueOf(quesProperty.getFirstProperty().getId()) + "-"
+                                + String.valueOf(quesProperty.getSecondProperty().getId()) + "-"
+                                + String.valueOf(question.getPublicity()) + "-" + question.getDifficulty();
+                    }
+                    propertyGroups.add(propertyGroup);
+                } else {
+                    // 有一级 无 二级
+                    propertyGroup = String.valueOf(quesProperty.getFirstProperty().getId()) + "-"
+                            + String.valueOf(question.getPublicity()) + "-" + question.getDifficulty();
+                    propertyGroups.add(propertyGroup);
+                }
+            }
+            return propertyGroups;
+        }
+        return null;
+    }
+
+    @Transactional
+    @Override
+    public StructInfo saveRandomPaper(RandomPaperDomain domain) {
+        if (domain.getCourseId() == null) {
+            throw new StatusException("课程id不能为空");
+        }
+        if (StringUtils.isBlank(domain.getName())) {
+            throw new StatusException("模板名称不能为空");
+        }
+        if (domain.getPaperStructType() == null) {
+            throw new StatusException("组卷模式不能为空");
+        }
+        if (domain.getPaperStructId() == null) {
+            throw new StatusException("组卷结构不能为空");
+        }
+        if (domain.getPaperType() == null) {
+            throw new StatusException("题源范围不能为空");
+        }
+        if (CollectionUtils.isEmpty(domain.getPaperIds())) {
+            throw new StatusException("试卷id不能为空");
+        }
+        RandomPaper rp = randomPaperRepo.findByRootOrgIdAndName(domain.getRootOrgId(), domain.getName());
+        if (rp != null && !rp.getId().equals(domain.getId())) {
+            throw new StatusException("模板名称已存在");
+        }
+        StructInfo ret = getPaperQuestionInfo(domain.getPaperStructId(), domain.getPaperIds());
+        if (ret.getValid()) {
+            RandomPaper e;
+            if (StringUtils.isNotBlank(domain.getId())) {
+                e = Model.of(randomPaperRepo.findById(domain.getId()));
+            } else {
+                e = new RandomPaper();
+                e.setCourseId(domain.getCourseId());
+                e.setEnable(true);
+                e.setRootOrgId(domain.getRootOrgId());
+            }
+            e.setName(domain.getName());
+            e.setPaperIds(domain.getPaperIds());
+            e.setPaperStructType(domain.getPaperStructType());
+            e.setPaperStructId(domain.getPaperStructId());
+            e.setPaperType(domain.getPaperType());
+            randomPaperRepo.save(e);
+            randomPaperQuestionRepo.deleteByRandomPaperId(e.getId());
+            List<RandomPaperQuestion> rqs = new ArrayList<>();
+            for (StructQuestionInfo si : ret.getStructQuestionInfo()) {
+                for (RandomPaperQuestionDto dto : si.getHardInfo().getQuestionInfo()) {
+                    addRqs(rqs, dto, e);
+                }
+                for (RandomPaperQuestionDto dto : si.getMediumInfo().getQuestionInfo()) {
+                    addRqs(rqs, dto, e);
+                }
+                for (RandomPaperQuestionDto dto : si.getEasyInfo().getQuestionInfo()) {
+                    addRqs(rqs, dto, e);
+                }
+            }
+            randomPaperQuestionRepo.saveAll(rqs);
+            String key = CacheConstants.CACHE_Q_RANDOM_PAPER + e.getId();
+            redisClient.delete(key);
+        }
+        clearQuestionIds(ret);
+        return ret;
+    }
+
+    private void addRqs(List<RandomPaperQuestion> rqs, RandomPaperQuestionDto dto, RandomPaper e) {
+        if (CollectionUtils.isNotEmpty(dto.getQuestionDtos())) {
+            for (QuestionDto qdto : dto.getQuestionDtos()) {
+                RandomPaperQuestion rq = new RandomPaperQuestion();
+                rqs.add(rq);
+                rq.setCourseId(e.getCourseId());
+                rq.setKey(dto.getDetailNumber() + "-" + dto.getKey());
+                rq.setQuestionId(qdto.getId());
+                rq.setRandomPaperId(e.getId());
+                rq.setRootOrgId(e.getRootOrgId());
+                rq.setScore(dto.getUnitScore());
+                rq.setQuestionType(qdto.getQuestionType());
+                rq.setAnswerType(qdto.getAnswerType());
+                if (CollectionUtils.isNotEmpty(qdto.getQuesOptions())) {
+                    rq.setOptionCount(qdto.getQuesOptions().size());
+                }
+                if (QuesStructType.NESTED_ANSWER_QUESTION.equals(rq.getQuestionType())
+                        && CollectionUtils.isNotEmpty(qdto.getSubQuestions())) {
+                    List<RandomPaperQuestion> subQuestion = new ArrayList<>();
+                    rq.setSubQuestions(subQuestion);
+                    List<Double> subScores = getSubScoreList(rq.getScore(), qdto.getSubQuestions().size());
+                    int i = 0;
+                    for (QuestionDto subQd : qdto.getSubQuestions()) {
+                        RandomPaperQuestion subrq = new RandomPaperQuestion();
+                        subQuestion.add(subrq);
+                        subrq.setCourseId(e.getCourseId());
+                        subrq.setScore(subScores.get(i));
+                        i++;
+                        subrq.setQuestionType(subQd.getQuestionType());
+                        subrq.setAnswerType(subQd.getAnswerType());
+                        if (CollectionUtils.isNotEmpty(subQd.getQuesOptions())) {
+                            subrq.setOptionCount(subQd.getQuesOptions().size());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private List<Double> getSubScoreList(double totalScore, int count) {
+        List<Double> scoreList = new ArrayList<>();
+        if (count > 0) {
+            int baseScore = (int) (totalScore / count);
+            double leftScore = totalScore;
+            for (int i = 0; i < count; i++) {
+                scoreList.add((double) baseScore);
+                leftScore -= baseScore;
+            }
+            if (leftScore > 0) {
+                scoreList.set(count - 1, baseScore + leftScore);
+            }
+            return scoreList;
+        }
+        return null;
+    }
+
+    @Override
+    public RandomPaperListVo getInfo(String id) {
+        RandomPaperListVo vo = this.mongoTemplate.findById(id, RandomPaperListVo.class, "randomPaper");
+        CourseCacheBean course = CacheHelper.getCourse(vo.getCourseId());
+        vo.setCourseCode(course.getCode());
+        vo.setCourseName(course.getName());
+        vo.setPaperStructTypeStr(vo.getPaperStructType().getName());
+        PaperStruct paperStruct = Model.of(paperStructRepo.findById(vo.getPaperStructId()));
+        vo.setPaperStructName(paperStruct.getName());
+        vo.setEnableStr(vo.getEnable() ? "启用" : "禁用");
+        GetUserReq ureq = new GetUserReq();
+        ureq.setUserId(vo.getUpdateBy());
+        GetUserResp ures = userCloudService.getUser(ureq);
+        vo.setUpdateByName(ures.getUserBean().getDisplayName());
+        Query query = new Query();
+        List<Object> ids = new ArrayList<>();
+        for (String pid : vo.getPaperIds()) {
+            if (pid.length() > 24) {
+                ids.add(pid);
+            } else {
+                ids.add(new ObjectId(pid));
+            }
+        }
+        query.addCriteria(Criteria.where("id").in(ids));
+        List<PaperVo> papers = this.mongoTemplate.find(query, PaperVo.class, "paper");
+        vo.setPapers(papers);
+        return vo;
+    }
+
+    @Override
+    public DefaultPaper getRandomPaper(String randomPaperId, Integer playTime) {
+        long start = System.currentTimeMillis();
+        RandomPaperCache rp = this.getRandomPaperTemplateCacheById(randomPaperId);
+        long start2 = System.currentTimeMillis();
+        log.warn("获取抽卷模板! 耗时:{}ms ID:{} 题数量:{}", start2 - start, randomPaperId, rp.getQuestionMap().size());
+
+        PaperStructCache ps = paperStructService.getPaperStructCacheById(rp.getPaperStructId());
+        log.warn("获取组卷结构! 耗时:{}ms 结构类型:{} ID:{}", System.currentTimeMillis() - start2, ps.getPaperStrucType(), rp.getPaperStructId());
+
+        CreateDefaultPaperParam param = new CreateDefaultPaperParam();
+        param.setFullyObjective(true);
+        param.setRp(rp);
+        param.setPlayTime(playTime);
+        DefaultPaper paper = new DefaultPaper();
+        paper.setName(rp.getName());
+        List<DefaultQuestionGroup> details = new ArrayList<>();
+        paper.setQuestionGroupList(details);
+
+        if (PaperStructType.BLUEPRINT.equals(ps.getPaperStrucType())) {
+            if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+                int detailNumber = 0;
+                for (PaperDetailStructCache ds : ps.getPaperDetailStructs()) {
+                    DefaultQuestionGroup detail = new DefaultQuestionGroup();
+                    details.add(detail);
+                    detail.setGroupName(ds.getName());
+                    detail.setGroupScore(ds.getTotalScore());
+                    List<DefaultQuestionStructureWrapper> units = new ArrayList<>();
+                    detail.setQuestionWrapperList(units);
+                    detailNumber++;
+                    param.setUnits(units);
+                    param.setDetailNumber(detailNumber);
+                    for (CoursePropertyNumberDto cp : ds.getCoursePropertyNumberDtos()) {
+                        if (!cp.getDisable()) {
+                            param.setCp(cp);
+                            createUnitByBlueProp(param);
+                        }
+                    }
+                }
+            }
+        } else if (PaperStructType.EXACT.equals(ps.getPaperStrucType())) {
+            if (CollectionUtils.isNotEmpty(ps.getPaperDetailStructs())) {
+                int detailNumber = 0;
+                for (PaperDetailStructCache ds : ps.getPaperDetailStructs()) {
+                    DefaultQuestionGroup detail = new DefaultQuestionGroup();
+                    details.add(detail);
+                    detail.setGroupName(ds.getName());
+                    detail.setGroupScore(ds.getTotalScore());
+                    List<DefaultQuestionStructureWrapper> units = new ArrayList<>();
+                    detail.setQuestionWrapperList(units);
+                    detailNumber++;
+                    param.setUnits(units);
+                    param.setDetailNumber(detailNumber);
+                    int index = 0;
+                    for (PaperDetailUnitStructDto us : ds.getUnitStructs()) {
+                        index++;
+                        param.setIndex(index);
+                        param.setUs(us);
+                        createUnitByExact(param);
+                    }
+                }
+            }
+        }
+        paper.setFullyObjective(param.getFullyObjective());
+
+        log.warn("抽卷完成! 总耗时:{}ms ID:{}", System.currentTimeMillis() - start, randomPaperId);
+        return paper;
+    }
+
+    private void createUnitByExact(CreateDefaultPaperParam param) {
+        PaperDetailUnitStructDto us = param.getUs();
+        if (us.getNoPublicDifficulty() > 0) {
+            param.setUnitCount(us.getNoPublicDifficulty());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
+                    + QuestionDifficulty.HARD.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+        if (us.getPublicDifficulty() > 0) {
+            param.setUnitCount(us.getPublicDifficulty());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
+                    + QuestionDifficulty.HARD.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+
+        if (us.getNoPublicMedium() > 0) {
+            param.setUnitCount(us.getNoPublicMedium());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
+                    + QuestionDifficulty.MEDIUM.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+        if (us.getPublicMedium() > 0) {
+            param.setUnitCount(us.getPublicMedium());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
+                    + QuestionDifficulty.MEDIUM.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+        if (us.getNoPublicSimple() > 0) {
+            param.setUnitCount(us.getNoPublicSimple());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
+                    + QuestionDifficulty.EASY.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+        if (us.getPublicSimple() > 0) {
+            param.setUnitCount(us.getPublicSimple());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
+                    + QuestionDifficulty.EASY.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+    }
+
+    private void createUnitByBlueProp(CreateDefaultPaperParam param) {
+        CoursePropertyNumberDto cp = param.getCp();
+        if (cp.getNoPublicDifficulty() > 0) {
+            String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
+                    cp.getPropertyId(), false, QuestionDifficulty.HARD.getName());
+            param.setKey(key);
+            param.setUnitCount(cp.getNoPublicDifficulty());
+            createUnit(param);
+        }
+        if (cp.getPublicDifficulty() > 0) {
+            String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
+                    cp.getPropertyId(), true, QuestionDifficulty.HARD.getName());
+            param.setKey(key);
+            param.setUnitCount(cp.getPublicDifficulty());
+            createUnit(param);
+        }
+
+        if (cp.getNoPublicMedium() > 0) {
+            String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
+                    cp.getPropertyId(), false, QuestionDifficulty.MEDIUM.getName());
+            param.setKey(key);
+            param.setUnitCount(cp.getNoPublicMedium());
+            createUnit(param);
+        }
+        if (cp.getPublicMedium() > 0) {
+            String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
+                    cp.getPropertyId(), true, QuestionDifficulty.MEDIUM.getName());
+            param.setKey(key);
+            param.setUnitCount(cp.getPublicMedium());
+            createUnit(param);
+        }
+        if (cp.getNoPublicSimple() > 0) {
+            String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
+                    cp.getPropertyId(), false, QuestionDifficulty.EASY.getName());
+            param.setKey(key);
+            param.setUnitCount(cp.getNoPublicSimple());
+            createUnit(param);
+        }
+        if (cp.getPublicSimple() > 0) {
+            String key = param.getDetailNumber() + "-" + bulidPropertyGroupByBlueStruct(cp.getPropertyParentId(),
+                    cp.getPropertyId(), true, QuestionDifficulty.EASY.getName());
+            param.setKey(key);
+            param.setUnitCount(cp.getPublicSimple());
+            createUnit(param);
+        }
+    }
+
+    private void createUnit(CreateDefaultPaperParam param) {
+        List<RandomPaperQuestion> rpqs = param.getRp().getQuestionMap().get(param.getKey());
+        Collections.shuffle(rpqs);
+        for (int i = 0; i < param.getUnitCount(); i++) {
+            RandomPaperQuestion rpq = rpqs.get(i);
+            DefaultQuestionStructureWrapper qw = new DefaultQuestionStructureWrapper();
+            param.getUnits().add(qw);
+            qw.setLimitedPlayTimes(param.getPlayTime());
+            qw.setPlayedTimes(0);
+            qw.setQuestionId(rpq.getQuestionId());
+            qw.setQuestionScore(rpq.getScore());
+            List<DefaultQuestionUnitWrapper> qList = new ArrayList<>();
+            qw.setQuestionUnitWrapperList(qList);
+            if (QuesStructType.NESTED_ANSWER_QUESTION.equals(rpq.getQuestionType())) {
+                for (RandomPaperQuestion sub : rpq.getSubQuestions()) {
+                    DefaultQuestionUnitWrapper q = new DefaultQuestionUnitWrapper();
+                    qList.add(q);
+                    q.setAnswerType(sub.getAnswerType());
+                    q.setOptionPermutation(getOption(sub.getOptionCount()));
+                    q.setQuestionScore(sub.getScore());
+                    q.setQuestionType(getByOldType(sub.getQuestionType()));
+                    if (!PaperUtil.isObjecttive(sub.getQuestionType())) {
+                        param.setFullyObjective(false);
+                    }
+                }
+            } else {
+                DefaultQuestionUnitWrapper q = new DefaultQuestionUnitWrapper();
+                qList.add(q);
+                q.setAnswerType(rpq.getAnswerType());
+                q.setOptionPermutation(getOption(rpq.getOptionCount()));
+                q.setQuestionScore(rpq.getScore());
+                q.setQuestionType(getByOldType(rpq.getQuestionType()));
+                if (!PaperUtil.isObjecttive(rpq.getQuestionType())) {
+                    param.setFullyObjective(false);
+                }
+            }
+        }
+    }
+
+    private QuestionType getByOldType(QuesStructType quesStructType) {
+        if (quesStructType == QuesStructType.BOOL_ANSWER_QUESTION) {
+            return QuestionType.TRUE_OR_FALSE;
+        }
+        if (quesStructType == QuesStructType.FILL_BLANK_QUESTION) {
+            return QuestionType.FILL_UP;
+        }
+        if (quesStructType == QuesStructType.MULTIPLE_ANSWER_QUESTION) {
+            return QuestionType.MULTIPLE_CHOICE;
+        }
+        if (quesStructType == QuesStructType.SINGLE_ANSWER_QUESTION) {
+            return QuestionType.SINGLE_CHOICE;
+        }
+        if (quesStructType == QuesStructType.TEXT_ANSWER_QUESTION) {
+            return QuestionType.ESSAY;
+        }
+        return null;
+    }
+
+    private Integer[] getOption(Integer count) {
+        if (count == null) {
+            return null;
+        }
+        Integer[] ret = new Integer[count];
+        for (int i = 0; i < count; i++) {
+            ret[i] = i;
+        }
+        return ret;
+    }
+
+    private RandomPaperCache getRandomPaperTemplateCacheById(String randomPaperId) {
+        // 抽卷模板 优先从本地缓存中获取
+        String key = CacheConstants.CACHE_Q_RANDOM_PAPER + randomPaperId;
+        RandomPaperCache rp = localRandomPaperCache.getIfPresent(key);
+        if (rp != null) {
+            log.warn("从【本地缓存】中获取抽卷模板! key:{}", key);
+            return rp;
+        }
+
+        // 从redis缓存中获取
+        rp = redisClient.get(key, RandomPaperCache.class, cacheTimeOut);
+        if (rp != null) {
+            localRandomPaperCache.put(key, rp);
+            log.warn("从【Redis缓存】中获取抽卷模板! key:{}", key);
+            return rp;
+        }
+
+        // 从数据库中获取
+        RandomPaper entity = Model.of(randomPaperRepo.findById(randomPaperId));
+        if (entity == null) {
+            throw new StatusException("未找到随机模板:" + randomPaperId);
+        }
+
+        List<RandomPaperQuestion> rpQuestions = randomPaperQuestionRepo.findByRandomPaperId(randomPaperId);
+        if (CollectionUtils.isEmpty(rpQuestions)) {
+            throw new StatusException("随机模板试题库为空:" + randomPaperId);
+        }
+
+        log.warn("从【数据库】中获取抽卷模板! key:{}", key);
+        rp = new RandomPaperCache();
+        rp.setName(entity.getName());
+        rp.setPaperStructId(entity.getPaperStructId());
+
+        Map<String, List<RandomPaperQuestion>> map = new HashMap<>();
+        for (RandomPaperQuestion rpq : rpQuestions) {
+            List<RandomPaperQuestion> list = map.get(rpq.getKey());
+            if (list == null) {
+                list = new ArrayList<>();
+                map.put(rpq.getKey(), list);
+            }
+            list.add(rpq);
+        }
+        rp.setQuestionMap(map);
+
+        redisClient.set(key, rp, cacheTimeOut);
+        localRandomPaperCache.put(key, rp);
+        return rp;
+    }
+
+    @Override
+    public boolean existStruct(String paperStructId) {
+        Query query = new Query();
+        query.addCriteria(Criteria.where("paperStructId").is(paperStructId));
+        RandomPaper rp = mongoTemplate.findOne(query, RandomPaper.class);
+        return rp != null;
+    }
+
+    @Override
+    public boolean existPaper(Long courseId, String paperId) {
+        Query query = new Query();
+        query.addCriteria(Criteria.where("courseId").is(courseId));
+        List<RandomPaper> rps = mongoTemplate.find(query, RandomPaper.class);
+        if (CollectionUtils.isEmpty(rps)) {
+            return false;
+        }
+        for (RandomPaper rp : rps) {
+            if (rp.getPaperIds().contains(paperId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 3 - 3
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/temp/CqdxService.java

@@ -13,9 +13,9 @@ import java.util.regex.Pattern;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
-import cn.com.qmth.examcloud.web.config.SystemProperties;
 import org.apache.commons.collections4.map.HashedMap;
 import org.apache.commons.lang3.StringUtils;
+import org.bson.types.ObjectId;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,7 +29,6 @@ import org.w3c.dom.NodeList;
 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.CommonUtils;
-import cn.com.qmth.examcloud.core.questions.base.IdUtils;
 import cn.com.qmth.examcloud.core.questions.base.converter.utils.FileUtil;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperStatus;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
@@ -46,6 +45,7 @@ import cn.com.qmth.examcloud.core.questions.dao.entity.QuesOption;
 import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
 import cn.com.qmth.examcloud.core.questions.service.QuesTypeNameService;
 import cn.com.qmth.examcloud.core.questions.service.impl.CourseService;
+import cn.com.qmth.examcloud.web.config.SystemProperties;
 
 @Component
 public class CqdxService {
@@ -537,7 +537,7 @@ public class CqdxService {
 					for (int s = 0; s < subQuesCount; s++) {
 						QuestionsTemp subQuesTmp = (QuestionsTemp) quesTemp.getSubQues().get(String.valueOf(s + 1));
 						Question subQuestion = new Question();
-						subQuestion.setId(IdUtils.uuid());
+						subQuestion.setId(new ObjectId().toString());
 						subQuestion.setQuestionType(subQuesTmp.getType());
 						subQuestion.setDifficulty("中");
 						subQuestion.setDifficultyDegree(0.5);

+ 3 - 6
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/themispaper/ThemisBlock.java

@@ -14,10 +14,7 @@ public class ThemisBlock {
      * video:视频
      */
     private String type;
-    /**
-     * 资源相对路径
-     */
-    private String value;
+    private Object value;
 
     private Map<String, Object> param;
 
@@ -29,11 +26,11 @@ public class ThemisBlock {
         this.type = type;
     }
 
-    public String getValue() {
+    public Object getValue() {
         return value;
     }
 
-    public void setValue(String value) {
+    public void setValue(Object value) {
         this.value = value;
     }
 

+ 52 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/util/BatchGetDataUtil.java

@@ -0,0 +1,52 @@
+package cn.com.qmth.examcloud.core.questions.service.util;
+
+import java.util.List;
+
+/**
+ *	多次批量获取数据
+ *	需重写getData方法
+ * @author xiatian
+ * @param <R> 结果类
+ * @param <P> 参数类
+ */
+public  class BatchGetDataUtil<R,P> {
+	/**
+	 * @param resultList 全部结果集合
+	 * @param paramList 全部参数集合
+	 * @param batchSize 每批参数数量
+	 */
+	public final void getDataForBatch(List<R> resultList,List<P> paramList,int batchSize) {
+		if(resultList==null||paramList==null||paramList.size()==0) {
+			return;
+		}
+		if(paramList.size()<=batchSize) {
+			List<R> temlist = getData(paramList);
+			if(temlist!=null&&temlist.size()>0) {
+				resultList.addAll(temlist);
+			}
+		}else {
+			int size = paramList.size();
+			int len=batchSize;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<P> subList = paramList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				List<R> temlist = getData(subList);
+				if(temlist!=null&&temlist.size()>0) {
+					resultList.addAll(temlist);
+				}
+			}
+		}
+	}
+	/**
+	 * 	Need Override
+	 * 	每批获取数据方法
+	 * @param <R>
+	 * @param <P>
+	 * @param paramList 获取每批数据时参数
+	 * @return
+	 */
+	public  List<R> getData(List<P> paramList) {
+		return null;
+	}
+}

+ 29 - 0
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/util/BatchSetDataUtil.java

@@ -0,0 +1,29 @@
+package cn.com.qmth.examcloud.core.questions.service.util;
+
+import java.util.List;
+
+public abstract class BatchSetDataUtil<P> {
+	/**
+	 * @param dataList 待填充的对象集合
+	 * @param batchSize 每批数量
+	 */
+	public final void setDataForBatch(List<P> dataList, int batchSize) {
+		if (dataList == null || dataList.size() == 0) {
+			return;
+		}
+		if (dataList.size() <= batchSize) {
+			setData(dataList);
+		} else {
+			int size = dataList.size();
+			int len = batchSize;
+			int count = (size + len - 1) / len;
+
+			for (int i = 0; i < count; i++) {
+				List<P> subList = dataList.subList(i * len, ((i + 1) * len > size ? size : len * (i + 1)));
+				setData(subList);
+			}
+		}
+	}
+
+	protected abstract void setData(List<P> dataList);
+}

+ 107 - 1
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/util/PaperUtil.java

@@ -10,6 +10,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperSeqMode;
 import cn.com.qmth.examcloud.core.questions.base.question.enums.QuesStructType;
 import cn.com.qmth.examcloud.core.questions.dao.ExportServiceManageRepo;
@@ -21,6 +22,7 @@ import cn.com.qmth.examcloud.core.questions.dao.entity.Question;
 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.export.ExportPaperAbstractService;
+import cn.com.qmth.examcloud.question.commons.core.question.AnswerType;
 import cn.com.qmth.examcloud.web.support.SpringContextHolder;
 
 public class PaperUtil {
@@ -209,7 +211,7 @@ public class PaperUtil {
 			}
 		}
 	}
-	
+
 	private static void setQuestionSeqMode4(List<PaperDetailExp> paperDetailExps) {
 		for (PaperDetailExp paperDetail : paperDetailExps) {
 			int seq = 0;
@@ -414,4 +416,108 @@ public class PaperUtil {
 		return sb.toString();
 	}
 
+	public static Boolean isObjecttive(QuesStructType type) {
+		if (QuesStructType.SINGLE_ANSWER_QUESTION.equals(type) || QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(type)
+				|| QuesStructType.BOOL_ANSWER_QUESTION.equals(type)) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+	
+	public static void checkUpdate(PaperDetailUnitExp updateUnit,PaperDetailUnit baseUnit,String msg) {
+    	Question baseQuestion = baseUnit.getQuestion();
+        Question updateQuestion = updateUnit.getQuestion();
+    	if (baseUnit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+            if (updateQuestion.getId().equals(baseQuestion.getId())) {
+            	return;
+            } else {
+                int size = baseQuestion.getSubQuestions().size();
+                // 判断更新的对象是哪个子题
+                for (int index = 1; index <= size; index++) {
+
+                    Question sub = baseQuestion.getSubQuestions().get(index - 1);
+                    if (updateQuestion.getId().equals(sub.getId())) {
+                    	if(updateQuestion.getScore().doubleValue()!=baseUnit.getSubScoreList().get(index - 1).doubleValue()) {
+                    		throw new StatusException("500", msg+"不能修改分数");
+                    	}
+                        checkUpdate(updateQuestion, sub,msg);
+
+                    }
+                }
+            }
+        } else {
+        	if(updateUnit.getScore().doubleValue()!=baseUnit.getScore().doubleValue()) {
+        		throw new StatusException("500", msg+"不能修改分数");
+        	}
+        	checkUpdate(updateQuestion, baseQuestion,msg);
+        }
+    }
+	public static void checkUpdate(Question update,Question base,String msg) {
+    	if(QuesStructType.SINGLE_ANSWER_QUESTION.equals(base.getQuestionType())
+    			||QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(base.getQuestionType())){
+	    	if(update.getQuesOptions().size()!=base.getQuesOptions().size()) {
+	    		throw new StatusException("500", msg+"不能修改选项数量");
+	    	}
+    	}
+    	if(QuesStructType.SINGLE_ANSWER_QUESTION.equals(base.getQuestionType())
+    			||QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(base.getQuestionType())
+    			||QuesStructType.BOOL_ANSWER_QUESTION.equals(base.getQuestionType())){
+	    	if(!update.getQuesAnswer().equals(base.getQuesAnswer())) {
+	    		throw new StatusException("500", msg+"不能修改客观题答案");
+	    	}
+    	}
+    	if(!answerTypeEquals(update.getAnswerType(),base.getAnswerType())) {
+    		throw new StatusException("500", msg+"不能修改作答类型");
+    	}
+    }
+	
+	private static boolean answerTypeEquals(AnswerType now,AnswerType old) {
+		if(now==null&&old==null) {
+			return true;
+		}
+		if(now==null&&old!=null) {
+			return false;
+		}
+		if(AnswerType.DIVERSIFIED_TEXT.equals(now)&&old==null) {
+			return true;
+		}
+		return now.equals(old);
+	}
+    
+    public static void checkUpdateOption(PaperDetailUnitExp updateUnit,PaperDetailUnit baseUnit,String msg) {
+    	Question baseQuestion = baseUnit.getQuestion();
+        Question updateQuestion = updateUnit.getQuestion();
+    	if (baseUnit.getQuestionType() == QuesStructType.NESTED_ANSWER_QUESTION) {
+            if (updateQuestion.getId().equals(baseQuestion.getId())) {
+            	return;
+            } else {
+                int size = baseQuestion.getSubQuestions().size();
+                // 判断更新的对象是哪个子题
+                for (int index = 1; index <= size; index++) {
+
+                    Question sub = baseQuestion.getSubQuestions().get(index - 1);
+                    if (updateQuestion.getId().equals(sub.getId())) {
+                    	checkUpdateOption(updateQuestion, sub,msg);
+
+                    }
+                }
+            }
+        } else {
+        	checkUpdateOption(updateQuestion, baseQuestion,msg);
+        }
+    }
+    
+    public static void checkUpdateOption(Question update,Question base,String msg) {
+    	if(QuesStructType.SINGLE_ANSWER_QUESTION.equals(base.getQuestionType())
+    			||QuesStructType.MULTIPLE_ANSWER_QUESTION.equals(base.getQuestionType())){
+	    	if(update.getQuesOptions().size()!=base.getQuesOptions().size()) {
+	    		throw new StatusException("500", msg+"不能修改选项数量");
+	    	}
+    	}
+    	if(!answerTypeEquals(update.getAnswerType(),base.getAnswerType())) {
+    		throw new StatusException("500", msg+"不能修改作答类型");
+    	}
+    }
+
 }

+ 12 - 0
examcloud-core-questions-starter/pom.xml

@@ -17,6 +17,18 @@
             <artifactId>examcloud-core-questions-api-provider</artifactId>
             <version>${project.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework.boot</groupId>
+                    <artifactId>spring-boot-starter-logging</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 
     <build>

+ 2 - 1
examcloud-core-questions-starter/src/main/java/cn/com/qmth/examcloud/core/questions/starter/config/ExamCloudResourceManager.java

@@ -14,6 +14,7 @@ import cn.com.qmth.examcloud.commons.util.RegExpUtil;
 import cn.com.qmth.examcloud.core.basic.api.UserDataRuleCloudService;
 import cn.com.qmth.examcloud.core.basic.api.request.QueryUserDataRuleReq;
 import cn.com.qmth.examcloud.core.basic.api.response.QueryUserDataRuleResp;
+import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.cache.CacheHelper;
 import cn.com.qmth.examcloud.support.cache.bean.AppCacheBean;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
@@ -104,7 +105,7 @@ public class ExamCloudResourceManager implements ResourceManager {
         Set<String> rolePrivilegeList = Sets.newHashSet();
         Long rootOrgId = user.getRootOrgId();
         for (Role role : roleList) {
-            String key = "$_P_" + rootOrgId + "_" + role.getRoleId();
+            String key = CacheConstants.CACHE_B_ROLE_PRIVILEGE + rootOrgId + "_" + role.getRoleId();
             String rolePrivileges = redisClient.get(key, String.class);
 
             List<String> rpList = RegExpUtil.findAll(rolePrivileges, "\\w+");

+ 22 - 1
jenkins.sh

@@ -1,8 +1,29 @@
 #!/bin/bash
 
 pwd
+PROJECT_NAME="examcloud-core-questions"
+
 mkdir -p ~/packages
+cp $PROJECT_NAME-starter/target/$PROJECT_NAME-distribution.zip ~/packages
+
+if [ "deploy" == "$1" ]; then
+  echo "deploy & restart..."
+  mkdir -p ~/project/examcloud
+  cp ~/packages/$PROJECT_NAME-distribution.zip ~/project/examcloud
+
+  cd ~/project/examcloud
+  rm -rf $PROJECT_NAME/lib
+  rm -rf $PROJECT_NAME/config
+
+  unzip -o -q $PROJECT_NAME-distribution.zip
+
+  cd $PROJECT_NAME
+  bash stop.sh
+  BUILD_ID=DONTKILLME
 
-cp examcloud-core-questions-starter/target/examcloud-core-questions-distribution.zip ~/packages
+  sleep 10s
+  bash start.sh
+  BUILD_ID=DONTKILLME
+fi
 
 echo "finished..."