Преглед изворни кода

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

deason пре 1 недеља
родитељ
комит
59e5503a2f

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

@@ -64,7 +64,7 @@ public class CoursePropertyController extends ControllerSupport {
         User user = getAccessUser();
         UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
 
-        List<CourseProperty> courseProperties = coursePropertyService.findAllByOrgId(user.getRootOrgId(),ud);
+        List<CourseProperty> courseProperties = coursePropertyService.findAllByOrgId(user.getRootOrgId(), ud);
         return new ResponseEntity<>(courseProperties, HttpStatus.OK);
     }
 
@@ -72,14 +72,15 @@ public class CoursePropertyController extends ControllerSupport {
     @ApiOperation(value = "根据orgId查询所有课程属性(分页)")
     @GetMapping(value = "/courseProperty/all/{curPage}/{pageSize}")
     public ResponseEntity<Object> findAllByOrgId(@ModelAttribute CoursePropertyDto coursePropertyDto,
-                                                 @PathVariable Integer curPage, @PathVariable Integer pageSize) {
+            @PathVariable Integer curPage, @PathVariable Integer pageSize) {
         User user = getAccessUser();
         UserDataRule userDataRule = super.getUserDataRule(DataRuleType.COURSE);
 
         coursePropertyDto.setOrgId(user.getRootOrgId());
 
         PageRequest pageable = PageRequest.of(curPage - 1, pageSize);
-        Page<CourseProperty> coursePropertiesPage = coursePropertyService.findList(coursePropertyDto, pageable, userDataRule);
+        Page<CourseProperty> coursePropertiesPage = coursePropertyService.findList(coursePropertyDto, pageable,
+                userDataRule);
 
         return new ResponseEntity<>(coursePropertiesPage, HttpStatus.OK);
     }
@@ -170,7 +171,8 @@ public class CoursePropertyController extends ControllerSupport {
             throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
         }
 
-        List<CourseProperty> courseProperties = coursePropertyService.findAllByCourseCode(courseCode);
+        List<CourseProperty> courseProperties = coursePropertyService.findAllByCourseCode(courseCode,
+                user.getRootOrgId());
         return new ResponseEntity<>(courseProperties, HttpStatus.OK);
     }
 
@@ -184,37 +186,39 @@ public class CoursePropertyController extends ControllerSupport {
             throw new StatusException(Constants.SYS_CODE_500, "请先登录!");
         }
 
-        List<CourseProperty> courseProperties = coursePropertyService.findByEnable(courseCode, true,user,ud);
+        List<CourseProperty> courseProperties = coursePropertyService.findByEnable(courseCode, true, user, ud);
         return new ResponseEntity<>(courseProperties, HttpStatus.OK);
     }
-    
+
     @ApiOperation(value = "下载导入模板", notes = "下载导入模板")
-	@GetMapping("/courseProperty/importTemplate")
-	public void getImportTemplate(HttpServletResponse response) {
-		String resoucePath = PathUtil.getResoucePath("templates/coursePropertyImportTemplate.xlsx");
-		exportFile("课程属性导入模板.xlsx", new File(resoucePath));
-	}
-
-	@DataRule(type = DataRuleType.COURSE)
-	@ApiOperation(value = "导入课程属性", notes = "导入")
-	@PostMapping("/courseProperty/import")
-	public Map<String, Object> importCourseProperty(@RequestPart @NotNull(message = "上传文件不能为空!") MultipartFile file) {
-		User user = getAccessUser();
-		UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
-		List<Map<String, Object>> failRecords = coursePropertyService.importCourseProperty(ud, user, getRootOrgId(), file);
-		Map<String, Object> map = Maps.newHashMap();
-		map.put("hasError", CollectionUtils.isNotEmpty(failRecords));
-		map.put("failRecords", failRecords);
-		return map;
-	}
-	
-	@DataRule(type = DataRuleType.COURSE)
+    @GetMapping("/courseProperty/importTemplate")
+    public void getImportTemplate(HttpServletResponse response) {
+        String resoucePath = PathUtil.getResoucePath("templates/coursePropertyImportTemplate.xlsx");
+        exportFile("课程属性导入模板.xlsx", new File(resoucePath));
+    }
+
+    @DataRule(type = DataRuleType.COURSE)
+    @ApiOperation(value = "导入课程属性", notes = "导入")
+    @PostMapping("/courseProperty/import")
+    public Map<String, Object> importCourseProperty(@RequestPart @NotNull(message = "上传文件不能为空!") MultipartFile file) {
+        User user = getAccessUser();
+        UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
+        List<Map<String, Object>> failRecords = coursePropertyService.importCourseProperty(ud, user, getRootOrgId(),
+                file);
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("hasError", CollectionUtils.isNotEmpty(failRecords));
+        map.put("failRecords", failRecords);
+        return map;
+    }
+
+    @DataRule(type = DataRuleType.COURSE)
     @ApiOperation(value = "导出课程属性")
     @RequestMapping(value = "/courseProperty/export", method = RequestMethod.GET)
-    public void exportCourseProperty(@RequestParam(required = false) String name,@RequestParam(required = false) Long courseId, HttpServletResponse response) {
+    public void exportCourseProperty(@RequestParam(required = false) String name,
+            @RequestParam(required = false) Long courseId, HttpServletResponse response) {
         User user = getAccessUser();
         UserDataRule ud = getUserDataRule(DataRuleType.COURSE);
-        List<CoursePropertyImportInfo> dtos = coursePropertyService.exportCourseProperty(ud, user, name,courseId);
+        List<CoursePropertyImportInfo> dtos = coursePropertyService.exportCourseProperty(ud, user, name, courseId);
         ExportUtils.exportEXCEL("课程属性", CoursePropertyImportInfo.class, dtos, response);
     }
 

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

@@ -43,9 +43,10 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
 
     @Autowired
     private ExtractConfigProviderService extractConfigExamService;
+
     @Autowired
     private ExtractConfigService extractConfigService;
-    
+
     @Autowired
     private RandomPaperService randomPaperService;
 
@@ -65,7 +66,8 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
             throw new StatusException("400", "groupCode is empty");
         }
 
-        ExtractConfigPaper result = extractConfigExamService.getDefaultPaper(paperReq.getExamId(), paperReq.getCourseCode(), paperReq.getGroupCode());
+        ExtractConfigPaper result = extractConfigExamService.getDefaultPaper(paperReq.getExamId(),
+                paperReq.getCourseCode(), paperReq.getGroupCode());
 
         GetPaperResp resp = new GetPaperResp();
         resp.setPaperId(result.getPaperId());
@@ -105,7 +107,6 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
         return resp;
     }
 
-
     @ApiOperation(value = "根据试卷id获取试卷结构")
     @PostMapping("getBasePaper")
     @Override
@@ -156,8 +157,8 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
             throw new StatusException("Q-010149", "questionId is null");
         }
 
-        DefaultQuestion defaultQuestion = extractConfigExamService.getDefaultQuestion(null, null, null,
-                id, "baseQuestion");
+        DefaultQuestion defaultQuestion = extractConfigExamService.getDefaultQuestion(null, null, null, id,
+                "baseQuestion");
 
         GetQuestionResp resp = new GetQuestionResp();
         resp.setDefaultQuestion(defaultQuestion);
@@ -182,30 +183,40 @@ public class ExtractConfigCloudServiceProvider implements ExtractConfigCloudServ
         }
         return null;
     }
-    
+
     @ApiOperation("随机抽卷")
     @PostMapping("randompaper")
     @Override
-	public GetRandomPaperResp getRandomPaper(@RequestBody GetRandomPaperReq req) {
-    	if (StringUtils.isBlank(req.getRandomPaperId())) {
+    public GetRandomPaperResp getRandomPaper(@RequestBody GetRandomPaperReq req) {
+        if (StringUtils.isBlank(req.getRandomPaperId())) {
             throw new StatusException("RandomPaperId is null");
         }
-    	if (req.getPlayTime()==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;
-	}
+        GetRandomPaperResp res = new GetRandomPaperResp();
+        DefaultPaper defaultPaper = randomPaperService.getRandomPaper(req.getRandomPaperId(), req.getPlayTime());
+        res.setDefaultPaper(defaultPaper);
+        return res;
+    }
 
     @ApiOperation("随机抽卷调卷规则创建")
     @PostMapping("randompaper/save")
     @Override
-	public SaveRandomPaperExtractConfigResp saveRandomPaperExtractConfig(@RequestBody SaveRandomPaperExtractConfigReq req) {
-    	extractConfigService.saveExtractConfigForRandomPaperByOuter(req);
-    	SaveRandomPaperExtractConfigResp res=new SaveRandomPaperExtractConfigResp();
-		return res;
-	}
+    public SaveRandomPaperExtractConfigResp saveRandomPaperExtractConfig(
+            @RequestBody SaveRandomPaperExtractConfigReq req) {
+        extractConfigService.saveExtractConfigForRandomPaperByOuter(req);
+        SaveRandomPaperExtractConfigResp res = new SaveRandomPaperExtractConfigResp();
+        return res;
+    }
+
+    @ApiOperation("获取考试下所有调卷规则")
+    @PostMapping("getExtractConfigBean")
+    @Override
+    public GetExtractConfigBeanResp getExtractConfigBean(@RequestBody GetExtractConfigBeanReq req) {
+        GetExtractConfigBeanResp res = new GetExtractConfigBeanResp();
+        res.setData(extractConfigService.getExtractConfigList(req.getExamId()));
+        return res;
+    }
 
 }

+ 18 - 16
examcloud-core-questions-dao/src/main/java/cn/com/qmth/examcloud/core/questions/dao/CoursePropertyRepo.java

@@ -12,38 +12,40 @@ import java.util.Set;
  * @describle 课程属性repo
  * @date 2017.11.2
  */
-public interface CoursePropertyRepo extends MongoRepository<CourseProperty, String>, QueryByExampleExecutor<CourseProperty> {
+public interface CoursePropertyRepo
+        extends MongoRepository<CourseProperty, String>, QueryByExampleExecutor<CourseProperty> {
 
-    //根据属性名查询
+    // 根据属性名查询
     CourseProperty findByOrgIdAndName(Long orgId, String name);
 
-    //根据课程id查询
+    // 根据课程id查询
     List<CourseProperty> findByCourseId(Long courseId);
 
-    List<CourseProperty> findByCourseCode(String courseCode);
+    List<CourseProperty> findByCourseCodeAndOrgId(String courseCode, Long rootOrgId);
 
-    //根据课程代码查询已开启的课程属性
+    // 根据课程代码查询已开启的课程属性
     List<CourseProperty> findByCourseCodeAndEnable(String courseCode, Boolean enable);
-    List<CourseProperty> findByOrgIdAndCourseCodeAndEnable(Long orgId,String courseCode, Boolean enable);
 
-    //根据课程id查询已经开启的课程属性
+    List<CourseProperty> findByOrgIdAndCourseCodeAndEnable(Long orgId, String courseCode, Boolean enable);
+
+    // 根据课程id查询已经开启的课程属性
     List<CourseProperty> findByCourseIdAndEnable(Long courseId, Boolean enable);
 
-    //根据id集合查询
+    // 根据id集合查询
     List<CourseProperty> findByIdIn(List<String> ids);
 
-	CourseProperty findByOrgIdAndCourseIdAndName(Long rootOrgId, Long courseId, String propertyName);
+    CourseProperty findByOrgIdAndCourseIdAndName(Long rootOrgId, Long courseId, String propertyName);
+
+    List<CourseProperty> findByOrgIdAndCourseIdIn(Long orgId, Set<Long> refIds);
 
-	List<CourseProperty> findByOrgIdAndCourseIdIn(Long orgId, Set<Long> refIds);
+    List<CourseProperty> findByOrgId(Long orgId);
 
-	List<CourseProperty> findByOrgId(Long orgId);
+    void deleteByOrgIdAndBatch(Long rootOrgId, String batch);
 
-	void deleteByOrgIdAndBatch(Long rootOrgId, String batch);
-	
-	CourseProperty findByOrgIdAndCourseIdAndBatch(Long rootOrgId,Long courseId, String batch);
+    CourseProperty findByOrgIdAndCourseIdAndBatch(Long rootOrgId, Long courseId, String batch);
 
-	List<CourseProperty> findByOrgIdAndCourseIdAndSourceId(Long rootOrgId, Long courseId, String sourceId);
+    List<CourseProperty> findByOrgIdAndCourseIdAndSourceId(Long rootOrgId, Long courseId, String sourceId);
 
-	List<CourseProperty> findAllByOrgIdAndCourseCode(Long rootOrgId, String courseCode);
+    List<CourseProperty> findAllByOrgIdAndCourseCode(Long rootOrgId, String courseCode);
 
 }

+ 8 - 7
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/CoursePropertyService.java

@@ -22,7 +22,8 @@ public interface CoursePropertyService {
 
     /**
      * 查询所有课程属性
-     * @param ud 
+     * 
+     * @param ud
      */
     List<CourseProperty> findAllByOrgId(Long orgId, UserDataRule ud);
 
@@ -44,12 +45,12 @@ public interface CoursePropertyService {
     /**
      * 根据课程code查询所有课程属性
      */
-    List<CourseProperty> findAllByCourseCode(String courseCode);
+    List<CourseProperty> findAllByCourseCode(String courseCode, Long rootOrgId);
 
     /**
      * 根据课程code查询所有开启的课程属性
      */
-    List<CourseProperty> findByEnable(String courseCode, Boolean enable,User user,UserDataRule ud);
+    List<CourseProperty> findByEnable(String courseCode, Boolean enable, User user, UserDataRule ud);
 
     /**
      * 批量启用/批量禁用
@@ -61,12 +62,12 @@ public interface CoursePropertyService {
      */
     void updateCoursePropertyStatus(String coursePropertyId, Boolean enable);
 
-	List<Map<String, Object>> importCourseProperty(UserDataRule ud, User user, Long rootOrgId, MultipartFile dataFile);
+    List<Map<String, Object>> importCourseProperty(UserDataRule ud, User user, Long rootOrgId, MultipartFile dataFile);
 
-	List<CoursePropertyImportInfo> exportCourseProperty(UserDataRule ud, User user, String name, Long courseId);
+    List<CoursePropertyImportInfo> exportCourseProperty(UserDataRule ud, User user, String name, Long courseId);
 
-	List<CourseProperty> findByOrgIdAndCourseIdAndSourceId(Long toRootOrgId, Long courseId, String id);
+    List<CourseProperty> findByOrgIdAndCourseIdAndSourceId(Long toRootOrgId, Long courseId, String id);
 
-	List<CourseProperty> findAllByOrgIdCourseCode(Long fromRootOrgId, String courseCode);
+    List<CourseProperty> findAllByOrgIdCourseCode(Long fromRootOrgId, String courseCode);
 
 }

+ 27 - 21
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/ExtractConfigService.java

@@ -7,6 +7,7 @@ import org.springframework.data.domain.Page;
 
 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.core.questions.api.bean.ExtractConfigBean;
 import cn.com.qmth.examcloud.core.questions.api.request.SaveRandomPaperExtractConfigReq;
 import cn.com.qmth.examcloud.core.questions.base.enums.PaperType;
 import cn.com.qmth.examcloud.core.questions.base.question.PaperDto;
@@ -24,6 +25,7 @@ import cn.com.qmth.examcloud.core.questions.service.bean.extract.SaveRandomPaper
  * @description 调卷规则接口
  */
 public interface ExtractConfigService {
+
     /**
      * 根据考试ID和课程code获取规则
      *
@@ -45,7 +47,8 @@ public interface ExtractConfigService {
      * 保存调卷规则
      *
      * @param extractConfig
-     * @param orgName       机构名称
+     * @param orgName
+     *            机构名称
      * @throws Exception
      */
     public Map<String, String> saveExtractConfig2(ExtractConfig extractConfig, User user) throws Exception;
@@ -59,13 +62,12 @@ public interface ExtractConfigService {
     public List<ExamPaper> saveExtractConfig(ExtractConfig extractConfig, User user);
 
     /**
-     * 按照设定调卷规则生成一套试卷
-     * 1.根据ExamPaper集合得出每个类型下应该抽取的试卷,并返回 试卷类型--->试卷的map
-     * 2.根据小题乱序和选项乱序规则,重组试卷
-     * 3.得到试卷类型--->试卷ID的map,设置到finishedPaperIdMap属性中
-     * 4.如果 是第一次生成试卷,保存finishedPaperIdMap
+     * 按照设定调卷规则生成一套试卷 1.根据ExamPaper集合得出每个类型下应该抽取的试卷,并返回 试卷类型--->试卷的map
+     * 2.根据小题乱序和选项乱序规则,重组试卷 3.得到试卷类型--->试卷ID的map,设置到finishedPaperIdMap属性中 4.如果
+     * 是第一次生成试卷,保存finishedPaperIdMap
      *
-     * @param extractConfigId 规则 ID
+     * @param extractConfigId
+     *            规则 ID
      * @return 类型--->试卷ID的Map集合
      */
     public Map<String, String> makePaperByConfig(ExtractConfig extractConfig);
@@ -73,9 +75,12 @@ public interface ExtractConfigService {
     /**
      * 根据给定试卷重组试卷,生成新的试卷
      *
-     * @param paper              给定的试卷
-     * @param upSetQuestionOrder 小题乱序		1:乱序  0:不乱序
-     * @param upSetOptionOrder   选项乱序		1:乱序  0:不乱序
+     * @param paper
+     *            给定的试卷
+     * @param upSetQuestionOrder
+     *            小题乱序 1:乱序 0:不乱序
+     * @param upSetOptionOrder
+     *            选项乱序 1:乱序 0:不乱序
      * @return
      */
     public Paper recombinationPaper(Paper paper, PaperType paperType, int upSetQuestionOrder, int upSetOptionOrder);
@@ -100,12 +105,11 @@ public interface ExtractConfigService {
      * @param id
      * @return
      */
-    public QuestionDto extractExamQuestion(String examId, String courseCode, String groupCode, String paperDetailUnitId);
+    public QuestionDto extractExamQuestion(String examId, String courseCode, String groupCode,
+            String paperDetailUnitId);
 
     /**
-     * 判断试卷中的题是否都为客观题(单选、多选、判断),包括套题中的小题
-     * 全为客观题返回true
-     * 不全为客观题返回false
+     * 判断试卷中的题是否都为客观题(单选、多选、判断),包括套题中的小题 全为客观题返回true 不全为客观题返回false
      *
      * @param newPaper
      * @return
@@ -121,7 +125,8 @@ public interface ExtractConfigService {
      * @param courseNo
      * @return
      */
-    public Page<ExtractConfig> findPageExtractConfig(int currentPage, int pageSize, Long examId, String courseCode, String orgId,UserDataRule ud );
+    public Page<ExtractConfig> findPageExtractConfig(int currentPage, int pageSize, Long examId, String courseCode,
+            String orgId, UserDataRule ud);
 
     /**
      * 根据试卷id获取试卷结构
@@ -130,7 +135,6 @@ public interface ExtractConfigService {
      */
     public Map<String, Object> extractPaper(String paperId);
 
-
     @Deprecated
     public PaperDto getPaperDtoByPaperNew(String basePaperId);
 
@@ -148,14 +152,16 @@ public interface ExtractConfigService {
      * @param examId
      * @return
      */
-    public List<CouresInfo> findCourseByExtractConfig(Long examId, String orgId,UserDataRule ud);
+    public List<CouresInfo> findCourseByExtractConfig(Long examId, String orgId, UserDataRule ud);
+
+    List<ExtractConfig> findExtractConfig(Long examId);
 
-	List<ExtractConfig> findExtractConfig(Long examId);
+    void saveExtractConfigForRandomPaper(ExtractConfig extractConfig);
 
-	void saveExtractConfigForRandomPaper(ExtractConfig extractConfig);
+    void saveExtractConfigForRandomPaperByOuter(SaveRandomPaperExtractConfigReq savereq);
 
-	void saveExtractConfigForRandomPaperByOuter(SaveRandomPaperExtractConfigReq savereq);
+    public void saveExtractConfigForRandomPaper(SaveRandomPaperExtractConfigForToolDomain req);
 
-	public void saveExtractConfigForRandomPaper(SaveRandomPaperExtractConfigForToolDomain req);
+    List<ExtractConfigBean> getExtractConfigList(Long examId);
 
 }

+ 357 - 350
examcloud-core-questions-service/src/main/java/cn/com/qmth/examcloud/core/questions/service/impl/CoursePropertyServiceImpl.java

@@ -61,36 +61,36 @@ import cn.com.qmth.examcloud.web.config.SystemProperties;
 @Service("coursePropertyService")
 public class CoursePropertyServiceImpl implements CoursePropertyService {
 
-	private static final Logger log = LoggerFactory.getLogger(CoursePropertyServiceImpl.class);
-	
-	private static final String[] EXCEL_HEADER = new String[] { "课程代码", "课程名称", "属性名称", "一级属性编号", "一级属性内容", "二级属性编号",
-	"二级属性内容" };
+    private static final Logger log = LoggerFactory.getLogger(CoursePropertyServiceImpl.class);
+
+    private static final String[] EXCEL_HEADER = new String[] { "课程代码", "课程名称", "属性名称", "一级属性编号", "一级属性内容", "二级属性编号",
+            "二级属性内容" };
 
     @Autowired
     private CoursePropertyRepo coursePropertyRepo;
-    
-	@Autowired
-	private PropertyRepo propertyRepo;
-    
+
+    @Autowired
+    private PropertyRepo propertyRepo;
+
     @Autowired
     private CourseCloudService courseCloudService;
 
     @Autowired
     private MongoTemplate mongoTemplate;
-    
+
     @Autowired
     private SystemProperties systemProperties;
 
     @Override
-    public List<CourseProperty> findAllByOrgId(Long orgId,UserDataRule ud) {
-    	if (ud.assertEmptyQueryResult()) {
+    public List<CourseProperty> findAllByOrgId(Long orgId, UserDataRule ud) {
+        if (ud.assertEmptyQueryResult()) {
             return Lists.newArrayList();
         }
-    	if (ud.assertNeedQueryRefIds()) {
-    		return coursePropertyRepo.findByOrgIdAndCourseIdIn(orgId,ud.getRefIds());
-    	}else {
-    		return coursePropertyRepo.findByOrgId(orgId);
-    	}
+        if (ud.assertNeedQueryRefIds()) {
+            return coursePropertyRepo.findByOrgIdAndCourseIdIn(orgId, ud.getRefIds());
+        } else {
+            return coursePropertyRepo.findByOrgId(orgId);
+        }
     }
 
     @Override
@@ -113,7 +113,7 @@ public class CoursePropertyServiceImpl implements CoursePropertyService {
         }
 
         if (StringUtils.isBlank(dto.getId())) {
-            dto.setId(null);//剔除空字符串ID(导致异常数据)
+            dto.setId(null);// 剔除空字符串ID(导致异常数据)
         }
 
         courseProperty = new CourseProperty();
@@ -141,7 +141,7 @@ public class CoursePropertyServiceImpl implements CoursePropertyService {
         }
 
         if (dto.getCourseId() != null) {
-            if (userDataRule.assertNeedQueryRefIds()&&!userDataRule.getRefIds().contains(dto.getCourseId())) {
+            if (userDataRule.assertNeedQueryRefIds() && !userDataRule.getRefIds().contains(dto.getCourseId())) {
                 return Page.empty();
             }
             query.addCriteria(Criteria.where("courseId").is(dto.getCourseId()));
@@ -188,26 +188,26 @@ public class CoursePropertyServiceImpl implements CoursePropertyService {
     }
 
     @Override
-    public List<CourseProperty> findAllByCourseCode(String courseCode) {
-        return coursePropertyRepo.findByCourseCode(courseCode);
+    public List<CourseProperty> findAllByCourseCode(String courseCode, Long rootOrgId) {
+        return coursePropertyRepo.findByCourseCodeAndOrgId(courseCode, rootOrgId);
     }
 
     @Override
-    public List<CourseProperty> findByEnable(String courseCode, Boolean enable,User user,UserDataRule ud) {
-        
+    public List<CourseProperty> findByEnable(String courseCode, Boolean enable, User user, UserDataRule ud) {
+
         if (ud.assertEmptyQueryResult()) {
-			return new ArrayList<>();
-		}
-		Query query = new Query();
-		if (ud.assertNeedQueryRefIds()) {
-			query.addCriteria(Criteria.where("courseId").in(ud.getRefIds()));
-		}
-		query.addCriteria(Criteria.where("orgId").is(user.getRootOrgId()));
-		query.addCriteria(Criteria.where("courseCode").is(courseCode));
-		
-		query.addCriteria(Criteria.where("enable").is(true));
-		List<CourseProperty> paperList = this.mongoTemplate.find(query, CourseProperty.class);
-		return paperList;
+            return new ArrayList<>();
+        }
+        Query query = new Query();
+        if (ud.assertNeedQueryRefIds()) {
+            query.addCriteria(Criteria.where("courseId").in(ud.getRefIds()));
+        }
+        query.addCriteria(Criteria.where("orgId").is(user.getRootOrgId()));
+        query.addCriteria(Criteria.where("courseCode").is(courseCode));
+
+        query.addCriteria(Criteria.where("enable").is(true));
+        List<CourseProperty> paperList = this.mongoTemplate.find(query, CourseProperty.class);
+        return paperList;
     }
 
     @Override
@@ -240,11 +240,12 @@ public class CoursePropertyServiceImpl implements CoursePropertyService {
         courseProperty.setUpdateTime(new Date());
         coursePropertyRepo.save(courseProperty);
     }
-    
+
     @Transactional
-	@Override
-	public List<Map<String, Object>> importCourseProperty(UserDataRule ud, User user, Long rootOrgId, MultipartFile dataFile) {
-    	File file = new File(systemProperties.getTempDataDir() + File.separator + UUID.randomUUID() + ".xlsx");
+    @Override
+    public List<Map<String, Object>> importCourseProperty(UserDataRule ud, User user, Long rootOrgId,
+            MultipartFile dataFile) {
+        File file = new File(systemProperties.getTempDataDir() + File.separator + UUID.randomUUID() + ".xlsx");
         try {
             file.createNewFile();
             dataFile.transferTo(file);
@@ -255,261 +256,268 @@ public class CoursePropertyServiceImpl implements CoursePropertyService {
             FileUtil.deleteFile(file.getAbsolutePath());
         }
     }
-    
-	private List<Map<String, Object>> disposeImportCourseProperty(UserDataRule ud, User user, Long rootOrgId, File file) {
-		List<String[]> lineList = null;
-		try {
-			lineList = ExcelReader.readSheetBySax(PathUtil.getCanonicalPath(file), 1, 7);
-		} catch (Exception e) {
-			throw new StatusException("100110", "Excel 解析失败",e );
-		}
-
-		if (CollectionUtils.isEmpty(lineList)) {
-			throw new StatusException("100111", "Excel无内容");
-		}
-
-		if (10001 < lineList.size()) {
-			throw new StatusException("100112", "数据行数不能超过10000");
-		}
-
-		List<Map<String, Object>> failRecords = Collections.synchronizedList(new ArrayList<Map<String, Object>>());
-
-		List<CoursePropertyImportInfo> courseList = Lists.newArrayList();
-		Map<String, Course> cousreMap = new HashMap<>();
-		for (int i = 0; i < lineList.size(); i++) {
-			String[] line = lineList.get(i);
-			if (0 == i) {
-				if (headerError(line)) {
-					throw new StatusException("100111", "Excel表头错误");
-				}
-				continue;
-			}
-
-			boolean hasError = false;
-			StringBuilder msg = new StringBuilder();
-
-			CoursePropertyImportInfo courseInfo = new CoursePropertyImportInfo();
-
-			String courseCode = trimAndNullIfBlank(line[0]);
-			if (StringUtils.isBlank(courseCode)) {
-				msg.append("  课程代码不能为空");
-				hasError = true;
-			} else if (!checkCourseExist(cousreMap, courseCode, rootOrgId)) {
-				msg.append("  课程代码不存在");
-				hasError = true;
-			} else if ("false".equals(cousreMap.get(courseCode).getEnable())) {
-				msg.append("  课程已禁用");
-				hasError = true;
-			}
-			
-			courseInfo.setCourseCode(courseCode);
-			
-			if(cousreMap.get(courseCode)!=null) {
-				courseInfo.setCourseId(Long.valueOf(cousreMap.get(courseCode).getId()));
-			}
-
-			String propertyName = trimAndNullIfBlank(line[2]);
-			if (StringUtils.isBlank(propertyName)) {
-				msg.append("  属性名称不能为空");
-				hasError = true;
-			} else if (propertyName.length() > 100) {
-				msg.append("  属性名称不能超过100个字符");
-				hasError = true;
-			}
-			courseInfo.setPropertyName(propertyName);
-
-			String firstCode = trimAndNullIfBlank(line[3]);
-			if (StringUtils.isBlank(firstCode)) {
-				msg.append("  一级属性编号不能为空");
-				hasError = true;
-			} else if (firstCode.length() > 100) {
-				msg.append("  一级属性编号不能超过100个字符");
-				hasError = true;
-			}
-			courseInfo.setFirstPropertyCode(firstCode);
-
-			String firstName = trimAndNullIfBlank(line[4]);
-			if (StringUtils.isNotBlank(firstName) && firstName.length() > 100) {
-				msg.append("  一级属性内容不能超过100个字符");
-				hasError = true;
-			}
-			courseInfo.setFirstPropertyName(firstName);
-
-			String secCode = trimAndNullIfBlank(line[5]);
-			if (StringUtils.isNotBlank(secCode) && secCode.length() > 100) {
-				msg.append("  二级属性编号不能超过100个字符");
-				hasError = true;
-			}
-			courseInfo.setSecondPropertyCode(secCode);
-
-			String secName = trimAndNullIfBlank(line[6]);
-			if (StringUtils.isNotBlank(secName) && secName.length() > 100) {
-				msg.append("  二级属性内容不能超过100个字符");
-				hasError = true;
-			}
-			courseInfo.setSecondPropertyName(secName);
-
-			if (hasError) {
-				failRecords.add(newError(i + 1, msg.toString()));
-			} else {
-				courseList.add(courseInfo);
-			}
-
-		}
-
-		if (CollectionUtils.isNotEmpty(failRecords)) {
-			return failRecords;
-		}
-
-		for (int i = 0; i < courseList.size(); i++) {
-			CoursePropertyImportInfo cur = courseList.get(i);
-			try {
-				saveCoursePropertyInfo(user, ud, cur);
-			} catch (StatusException e) {
-				failRecords.add(newError(i + 2, e.getDesc()));
-			} catch (Exception e) {
-				failRecords.add(newError(i + 2, "系统异常"));
-				log.error("课程属性导入系统异常", e);
-			}
-		}
-		if (CollectionUtils.isNotEmpty(failRecords)) {
-			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
-		}
-
-		return failRecords;
-	}
-
-	private void saveCoursePropertyInfo(User user, UserDataRule ud, CoursePropertyImportInfo cur) {
-		if(ud.assertEmptyQueryResult()) {
-			throw new StatusException(" 课程不在该账号负责的课程中");
-		}
-		if (ud.assertNeedQueryRefIds() && !ud.getRefIds().contains(cur.getCourseId())) {
-			throw new StatusException(" 课程不在该账号负责的课程中");
-		}
-		CourseProperty c = coursePropertyRepo.findByOrgIdAndCourseIdAndName(user.getRootOrgId(),cur.getCourseId(), cur.getPropertyName());
-		if(c==null) {
-			c=new CourseProperty();
-			c.setCourseId(cur.getCourseId());
-			c.setCourseCode(cur.getCourseCode());
-			c.setName(cur.getPropertyName());
-			c.setOrgId(user.getRootOrgId());
-			c.setEnable(true);
-		}
-		c.setUpdateTime(new Date());
-		coursePropertyRepo.save(c);
-		
-		Property first=propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdAndCode(user.getRootOrgId(), c.getId(), Property.ROOT_PARENT_ID, cur.getFirstPropertyCode());
-		if(first!=null) {
-			first.setName(cur.getFirstPropertyName());
-		}else {
-			if(StringUtils.isBlank(cur.getFirstPropertyName())) {
-				throw new StatusException(" 一级属性内容不能为空");
-			}
-			first=new Property();
-			first.setParentId(Property.ROOT_PARENT_ID);
-			first.setOrgId(user.getRootOrgId());
-			first.setCoursePropertyId(c.getId());
-			first.setCode(cur.getFirstPropertyCode());
-			first.setName(cur.getFirstPropertyName());
-			Integer number = 0;
-            List<Property> parentProperties = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdOrderByNumber(first.getOrgId(), first.getCoursePropertyId(), first.getParentId());
+
+    private List<Map<String, Object>> disposeImportCourseProperty(UserDataRule ud, User user, Long rootOrgId,
+            File file) {
+        List<String[]> lineList = null;
+        try {
+            lineList = ExcelReader.readSheetBySax(PathUtil.getCanonicalPath(file), 1, 7);
+        } catch (Exception e) {
+            throw new StatusException("100110", "Excel 解析失败", e);
+        }
+
+        if (CollectionUtils.isEmpty(lineList)) {
+            throw new StatusException("100111", "Excel无内容");
+        }
+
+        if (10001 < lineList.size()) {
+            throw new StatusException("100112", "数据行数不能超过10000");
+        }
+
+        List<Map<String, Object>> failRecords = Collections.synchronizedList(new ArrayList<Map<String, Object>>());
+
+        List<CoursePropertyImportInfo> courseList = Lists.newArrayList();
+        Map<String, Course> cousreMap = new HashMap<>();
+        for (int i = 0; i < lineList.size(); i++) {
+            String[] line = lineList.get(i);
+            if (0 == i) {
+                if (headerError(line)) {
+                    throw new StatusException("100111", "Excel表头错误");
+                }
+                continue;
+            }
+
+            boolean hasError = false;
+            StringBuilder msg = new StringBuilder();
+
+            CoursePropertyImportInfo courseInfo = new CoursePropertyImportInfo();
+
+            String courseCode = trimAndNullIfBlank(line[0]);
+            if (StringUtils.isBlank(courseCode)) {
+                msg.append("  课程代码不能为空");
+                hasError = true;
+            } else if (!checkCourseExist(cousreMap, courseCode, rootOrgId)) {
+                msg.append("  课程代码不存在");
+                hasError = true;
+            } else if ("false".equals(cousreMap.get(courseCode).getEnable())) {
+                msg.append("  课程已禁用");
+                hasError = true;
+            }
+
+            courseInfo.setCourseCode(courseCode);
+
+            if (cousreMap.get(courseCode) != null) {
+                courseInfo.setCourseId(Long.valueOf(cousreMap.get(courseCode).getId()));
+            }
+
+            String propertyName = trimAndNullIfBlank(line[2]);
+            if (StringUtils.isBlank(propertyName)) {
+                msg.append("  属性名称不能为空");
+                hasError = true;
+            } else if (propertyName.length() > 100) {
+                msg.append("  属性名称不能超过100个字符");
+                hasError = true;
+            }
+            courseInfo.setPropertyName(propertyName);
+
+            String firstCode = trimAndNullIfBlank(line[3]);
+            if (StringUtils.isBlank(firstCode)) {
+                msg.append("  一级属性编号不能为空");
+                hasError = true;
+            } else if (firstCode.length() > 100) {
+                msg.append("  一级属性编号不能超过100个字符");
+                hasError = true;
+            }
+            courseInfo.setFirstPropertyCode(firstCode);
+
+            String firstName = trimAndNullIfBlank(line[4]);
+            if (StringUtils.isNotBlank(firstName) && firstName.length() > 100) {
+                msg.append("  一级属性内容不能超过100个字符");
+                hasError = true;
+            }
+            courseInfo.setFirstPropertyName(firstName);
+
+            String secCode = trimAndNullIfBlank(line[5]);
+            if (StringUtils.isNotBlank(secCode) && secCode.length() > 100) {
+                msg.append("  二级属性编号不能超过100个字符");
+                hasError = true;
+            }
+            courseInfo.setSecondPropertyCode(secCode);
+
+            String secName = trimAndNullIfBlank(line[6]);
+            if (StringUtils.isNotBlank(secName) && secName.length() > 100) {
+                msg.append("  二级属性内容不能超过100个字符");
+                hasError = true;
+            }
+            courseInfo.setSecondPropertyName(secName);
+
+            if (hasError) {
+                failRecords.add(newError(i + 1, msg.toString()));
+            } else {
+                courseList.add(courseInfo);
+            }
+
+        }
+
+        if (CollectionUtils.isNotEmpty(failRecords)) {
+            return failRecords;
+        }
+
+        for (int i = 0; i < courseList.size(); i++) {
+            CoursePropertyImportInfo cur = courseList.get(i);
+            try {
+                saveCoursePropertyInfo(user, ud, cur);
+            } catch (StatusException e) {
+                failRecords.add(newError(i + 2, e.getDesc()));
+            } catch (Exception e) {
+                failRecords.add(newError(i + 2, "系统异常"));
+                log.error("课程属性导入系统异常", e);
+            }
+        }
+        if (CollectionUtils.isNotEmpty(failRecords)) {
+            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+        }
+
+        return failRecords;
+    }
+
+    private void saveCoursePropertyInfo(User user, UserDataRule ud, CoursePropertyImportInfo cur) {
+        if (ud.assertEmptyQueryResult()) {
+            throw new StatusException(" 课程不在该账号负责的课程中");
+        }
+        if (ud.assertNeedQueryRefIds() && !ud.getRefIds().contains(cur.getCourseId())) {
+            throw new StatusException(" 课程不在该账号负责的课程中");
+        }
+        CourseProperty c = coursePropertyRepo.findByOrgIdAndCourseIdAndName(user.getRootOrgId(), cur.getCourseId(),
+                cur.getPropertyName());
+        if (c == null) {
+            c = new CourseProperty();
+            c.setCourseId(cur.getCourseId());
+            c.setCourseCode(cur.getCourseCode());
+            c.setName(cur.getPropertyName());
+            c.setOrgId(user.getRootOrgId());
+            c.setEnable(true);
+        }
+        c.setUpdateTime(new Date());
+        coursePropertyRepo.save(c);
+
+        Property first = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdAndCode(user.getRootOrgId(), c.getId(),
+                Property.ROOT_PARENT_ID, cur.getFirstPropertyCode());
+        if (first != null) {
+            first.setName(cur.getFirstPropertyName());
+        } else {
+            if (StringUtils.isBlank(cur.getFirstPropertyName())) {
+                throw new StatusException(" 一级属性内容不能为空");
+            }
+            first = new Property();
+            first.setParentId(Property.ROOT_PARENT_ID);
+            first.setOrgId(user.getRootOrgId());
+            first.setCoursePropertyId(c.getId());
+            first.setCode(cur.getFirstPropertyCode());
+            first.setName(cur.getFirstPropertyName());
+            Integer number = 0;
+            List<Property> parentProperties = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdOrderByNumber(
+                    first.getOrgId(), first.getCoursePropertyId(), first.getParentId());
             if (parentProperties != null && parentProperties.size() > 0) {
                 number = parentProperties.get(parentProperties.size() - 1).getNumber();
             }
 
             first.setNumber(number + 1);
-		}
-		propertyRepo.save(first);
-		
-		if(StringUtils.isNotBlank(cur.getSecondPropertyCode())&&StringUtils.isBlank(cur.getSecondPropertyName())) {
-			throw new StatusException(" 二级属性内容不能为空");
-		}
-		if(StringUtils.isNotBlank(cur.getSecondPropertyCode())){
-			Property sec=propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdAndCode(user.getRootOrgId(), c.getId(), first.getId(), cur.getSecondPropertyCode());
-			if(sec!=null) {
-				sec.setName(cur.getSecondPropertyName());
-			}else {
-				sec=new Property();
-				sec.setParentId(first.getId());
-				sec.setOrgId(user.getRootOrgId());
-				sec.setCoursePropertyId(c.getId());
-				sec.setCode(cur.getSecondPropertyCode());
-				sec.setName(cur.getSecondPropertyName());
-				Integer number = 0;
-	            List<Property> parentProperties = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdOrderByNumber(sec.getOrgId(), sec.getCoursePropertyId(), sec.getParentId());
-	            if (parentProperties != null && parentProperties.size() > 0) {
-	                number = parentProperties.get(parentProperties.size() - 1).getNumber();
-	            }
-
-	            sec.setNumber(number + 1);
-			}
-			propertyRepo.save(sec);
-		}
-		
-	}
-
-	private boolean checkCourseExist(Map<String, Course> cousre, String courseCode, Long rootOrgId) {
-		if (cousre.get(courseCode) != null) {
-			return true;
-		}
-		GetCourseReq req=new GetCourseReq();
-		req.setRootOrgId(rootOrgId);
-		req.setCode(courseCode);
-		GetCourseResp res=courseCloudService.getCourse(req);
-		if (res.getCourseBean() == null) {
-			return false;
-		} else {
-			cousre.put(courseCode, of(res.getCourseBean()));
-			return true;
-		}
-	}
-	
-	private Course of(CourseBean b) {
-		Course c=new Course();
-		c.setCode(b.getCode());
-		c.setEnable(b.getEnable()!=null?null:b.getEnable().toString());
-		c.setId(b.getId()+"");
-		c.setLevel(b.getLevel());
-		c.setName(b.getName());
-		c.setOrgId(b.getRootOrgId()+"");
-		return c;
-	}
-
-	private Map<String, Object> newError(int lineNum, String msg) {
-		Map<String, Object> map = Maps.newHashMap();
-		map.put("lineNum", lineNum);
-		map.put("msg", msg);
-		return map;
-	}
-
-	private String trimAndNullIfBlank(String s) {
-		if (StringUtils.isBlank(s)) {
-			return null;
-		}
-		return s.trim();
-	}
-
-	/**
-	 * 方法注释
-	 *
-	 * @param header
-	 * @author WANGWEI
-	 */
-	private boolean headerError(String[] header) {
-		for (int i = 0; i < EXCEL_HEADER.length; i++) {
-			if (null == header[i]) {
-				return true;
-			}
-			if (!EXCEL_HEADER[i].equals(header[i].trim())) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	@Override
-	public List<CoursePropertyImportInfo> exportCourseProperty(UserDataRule userDataRule, User user, String name, Long courseId) {
-		if (userDataRule.assertEmptyQueryResult()) {
+        }
+        propertyRepo.save(first);
+
+        if (StringUtils.isNotBlank(cur.getSecondPropertyCode()) && StringUtils.isBlank(cur.getSecondPropertyName())) {
+            throw new StatusException(" 二级属性内容不能为空");
+        }
+        if (StringUtils.isNotBlank(cur.getSecondPropertyCode())) {
+            Property sec = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdAndCode(user.getRootOrgId(), c.getId(),
+                    first.getId(), cur.getSecondPropertyCode());
+            if (sec != null) {
+                sec.setName(cur.getSecondPropertyName());
+            } else {
+                sec = new Property();
+                sec.setParentId(first.getId());
+                sec.setOrgId(user.getRootOrgId());
+                sec.setCoursePropertyId(c.getId());
+                sec.setCode(cur.getSecondPropertyCode());
+                sec.setName(cur.getSecondPropertyName());
+                Integer number = 0;
+                List<Property> parentProperties = propertyRepo.findByOrgIdAndCoursePropertyIdAndParentIdOrderByNumber(
+                        sec.getOrgId(), sec.getCoursePropertyId(), sec.getParentId());
+                if (parentProperties != null && parentProperties.size() > 0) {
+                    number = parentProperties.get(parentProperties.size() - 1).getNumber();
+                }
+
+                sec.setNumber(number + 1);
+            }
+            propertyRepo.save(sec);
+        }
+
+    }
+
+    private boolean checkCourseExist(Map<String, Course> cousre, String courseCode, Long rootOrgId) {
+        if (cousre.get(courseCode) != null) {
+            return true;
+        }
+        GetCourseReq req = new GetCourseReq();
+        req.setRootOrgId(rootOrgId);
+        req.setCode(courseCode);
+        GetCourseResp res = courseCloudService.getCourse(req);
+        if (res.getCourseBean() == null) {
+            return false;
+        } else {
+            cousre.put(courseCode, of(res.getCourseBean()));
+            return true;
+        }
+    }
+
+    private Course of(CourseBean b) {
+        Course c = new Course();
+        c.setCode(b.getCode());
+        c.setEnable(b.getEnable() != null ? null : b.getEnable().toString());
+        c.setId(b.getId() + "");
+        c.setLevel(b.getLevel());
+        c.setName(b.getName());
+        c.setOrgId(b.getRootOrgId() + "");
+        return c;
+    }
+
+    private Map<String, Object> newError(int lineNum, String msg) {
+        Map<String, Object> map = Maps.newHashMap();
+        map.put("lineNum", lineNum);
+        map.put("msg", msg);
+        return map;
+    }
+
+    private String trimAndNullIfBlank(String s) {
+        if (StringUtils.isBlank(s)) {
+            return null;
+        }
+        return s.trim();
+    }
+
+    /**
+     * 方法注释
+     *
+     * @param header
+     * @author WANGWEI
+     */
+    private boolean headerError(String[] header) {
+        for (int i = 0; i < EXCEL_HEADER.length; i++) {
+            if (null == header[i]) {
+                return true;
+            }
+            if (!EXCEL_HEADER[i].equals(header[i].trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public List<CoursePropertyImportInfo> exportCourseProperty(UserDataRule userDataRule, User user, String name,
+            Long courseId) {
+        if (userDataRule.assertEmptyQueryResult()) {
             return Lists.newArrayList();
         }
 
@@ -532,70 +540,69 @@ public class CoursePropertyServiceImpl implements CoursePropertyService {
             query.addCriteria(Criteria.where("name").regex(name.trim()));
         }
 
-
         query.with(Sort.by(new Sort.Order(Sort.Direction.DESC, "updateTime")));
         List<CourseProperty> coursePros = mongoTemplate.find(query, CourseProperty.class);
 
-		List<CoursePropertyImportInfo> ret=new ArrayList<>();
-		if(!CollectionUtils.isEmpty(coursePros)) {
-			for(CourseProperty cp:coursePros) {
-				fillProperty(ret, cp);
-			}
-		}
-		return ret;
-	}
-	
-	private void fillProperty(List<CoursePropertyImportInfo> ret,CourseProperty cp) {
-		List<Property> ps=propertyRepo.findByCoursePropertyIdOrderByNumber(cp.getId());
-		if(!CollectionUtils.isEmpty(ps)) {
-			Map<String,List<Property>> secPs=new HashMap<>();
-			for(Property p:ps) {
-				if(!Property.ROOT_PARENT_ID.equals(p.getParentId())) {
-					List<Property> list=secPs.get(p.getParentId());
-					if(list==null) {
-						list=new ArrayList<>();
-						secPs.put(p.getParentId(), list);
-					}
-					list.add(p);
-				}
-			}
-			
-			for(Property p:ps) {
-				if(Property.ROOT_PARENT_ID.equals(p.getParentId())) {
-					CoursePropertyImportInfo info=new CoursePropertyImportInfo();
-					info.setCourseCode(cp.getCourseCode());
-					info.setCourseName(CacheHelper.getCourse(cp.getCourseId()).getName());
-					info.setPropertyName(cp.getName());
-					info.setFirstPropertyCode(p.getCode());
-					info.setFirstPropertyName(p.getName());
-					ret.add(info);
-					List<Property> secPsList=secPs.get(p.getId());
-					if(!CollectionUtils.isEmpty(secPsList)) {
-						for(Property sec:secPsList) {
-							CoursePropertyImportInfo secinfo=new CoursePropertyImportInfo();
-							secinfo.setCourseCode(cp.getCourseCode());
-							secinfo.setCourseName(CacheHelper.getCourse(cp.getCourseId()).getName());
-							secinfo.setPropertyName(cp.getName());
-							secinfo.setFirstPropertyCode(p.getCode());
-							secinfo.setFirstPropertyName(p.getName());
-							secinfo.setSecondPropertyCode(sec.getCode());
-							secinfo.setSecondPropertyName(sec.getName());
-							ret.add(secinfo);
-						}
-					}
-				}
-			}
-		}
-	}
-
-	@Override
-	public List<CourseProperty> findByOrgIdAndCourseIdAndSourceId(Long rootOrgId, Long courseId, String sourceId) {
-		return coursePropertyRepo.findByOrgIdAndCourseIdAndSourceId( rootOrgId, courseId, sourceId);
-	}
-
-	@Override
-	public List<CourseProperty> findAllByOrgIdCourseCode(Long rootOrgId, String courseCode) {
-		return coursePropertyRepo.findAllByOrgIdAndCourseCode( rootOrgId, courseCode);
-	}
+        List<CoursePropertyImportInfo> ret = new ArrayList<>();
+        if (!CollectionUtils.isEmpty(coursePros)) {
+            for (CourseProperty cp : coursePros) {
+                fillProperty(ret, cp);
+            }
+        }
+        return ret;
+    }
+
+    private void fillProperty(List<CoursePropertyImportInfo> ret, CourseProperty cp) {
+        List<Property> ps = propertyRepo.findByCoursePropertyIdOrderByNumber(cp.getId());
+        if (!CollectionUtils.isEmpty(ps)) {
+            Map<String, List<Property>> secPs = new HashMap<>();
+            for (Property p : ps) {
+                if (!Property.ROOT_PARENT_ID.equals(p.getParentId())) {
+                    List<Property> list = secPs.get(p.getParentId());
+                    if (list == null) {
+                        list = new ArrayList<>();
+                        secPs.put(p.getParentId(), list);
+                    }
+                    list.add(p);
+                }
+            }
+
+            for (Property p : ps) {
+                if (Property.ROOT_PARENT_ID.equals(p.getParentId())) {
+                    CoursePropertyImportInfo info = new CoursePropertyImportInfo();
+                    info.setCourseCode(cp.getCourseCode());
+                    info.setCourseName(CacheHelper.getCourse(cp.getCourseId()).getName());
+                    info.setPropertyName(cp.getName());
+                    info.setFirstPropertyCode(p.getCode());
+                    info.setFirstPropertyName(p.getName());
+                    ret.add(info);
+                    List<Property> secPsList = secPs.get(p.getId());
+                    if (!CollectionUtils.isEmpty(secPsList)) {
+                        for (Property sec : secPsList) {
+                            CoursePropertyImportInfo secinfo = new CoursePropertyImportInfo();
+                            secinfo.setCourseCode(cp.getCourseCode());
+                            secinfo.setCourseName(CacheHelper.getCourse(cp.getCourseId()).getName());
+                            secinfo.setPropertyName(cp.getName());
+                            secinfo.setFirstPropertyCode(p.getCode());
+                            secinfo.setFirstPropertyName(p.getName());
+                            secinfo.setSecondPropertyCode(sec.getCode());
+                            secinfo.setSecondPropertyName(sec.getName());
+                            ret.add(secinfo);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public List<CourseProperty> findByOrgIdAndCourseIdAndSourceId(Long rootOrgId, Long courseId, String sourceId) {
+        return coursePropertyRepo.findByOrgIdAndCourseIdAndSourceId(rootOrgId, courseId, sourceId);
+    }
+
+    @Override
+    public List<CourseProperty> findAllByOrgIdCourseCode(Long rootOrgId, String courseCode) {
+        return coursePropertyRepo.findAllByOrgIdAndCourseCode(rootOrgId, courseCode);
+    }
 
 }

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

@@ -1,5 +1,39 @@
 package cn.com.qmth.examcloud.core.questions.service.impl;
 
+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.collections4.CollectionUtils;
+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 cn.com.qmth.examcloud.api.commons.enums.CallType;
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.api.commons.security.bean.UserDataRule;
@@ -8,21 +42,47 @@ import cn.com.qmth.examcloud.core.oe.admin.api.ExamRecordCloudService;
 import cn.com.qmth.examcloud.core.oe.admin.api.request.CheckPaperInExamReq;
 import cn.com.qmth.examcloud.core.oe.admin.api.response.CheckPaperInExamResp;
 import cn.com.qmth.examcloud.core.oe.student.api.ExamRecordDataCloudService;
+import cn.com.qmth.examcloud.core.questions.api.bean.ExtractConfigBean;
+import cn.com.qmth.examcloud.core.questions.api.bean.ExtractConfigDetailBean;
 import cn.com.qmth.examcloud.core.questions.api.request.SaveRandomPaperExtractConfigReq;
 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.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.*;
+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.enums.QuesStructType;
-import cn.com.qmth.examcloud.core.questions.dao.*;
-import cn.com.qmth.examcloud.core.questions.dao.entity.*;
+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.RandomPaperRepo;
+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.entity.RandomPaper;
 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.RandomPaperService;
-import cn.com.qmth.examcloud.core.questions.service.bean.*;
+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.extract.SaveRandomPaperExtractConfigForToolDomain;
 import cn.com.qmth.examcloud.core.questions.service.bean.randompaper.RandomPaperListVo;
 import cn.com.qmth.examcloud.core.questions.service.cache.ExtractConfigCache;
@@ -38,32 +98,6 @@ import cn.com.qmth.examcloud.examwork.api.response.GetExamResp;
 import cn.com.qmth.examcloud.support.CacheConstants;
 import cn.com.qmth.examcloud.support.fss.FssHelper;
 import cn.com.qmth.examcloud.web.redis.RedisClient;
-import org.apache.commons.collections4.CollectionUtils;
-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 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
@@ -226,7 +260,8 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
         Example<RandomPaper> queryExample = Example.of(query);
         boolean exists = randomPaperRepo.exists(queryExample);
         if (!exists) {
-            LOG.warn("随机抽题模板选择错误,与当前课程不匹配! courseId:{} randomPaperId:{}", course.getId(), extractConfig.getRandomPaperId());
+            LOG.warn("随机抽题模板选择错误,与当前课程不匹配! courseId:{} randomPaperId:{}", course.getId(),
+                    extractConfig.getRandomPaperId());
             throw new StatusException("500", "随机抽题模板选择错误,与当前课程不匹配!");
         }
 
@@ -269,10 +304,10 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
             return;
         }
         for (ExamPaper paper : ec.getExamPaperList()) {
-            if (inExam(paper.getPaper().getId())) {//如果有考试记录
+            if (inExam(paper.getPaper().getId())) {// 如果有考试记录
                 updatePaperInUse(paper.getPaper().getId(), 1);
             } else {
-                if (!inOtherExtractConfig(extractConfigId, paper.getPaper().getId())) {//没有考试记录且不被其他调卷规则引用
+                if (!inOtherExtractConfig(extractConfigId, paper.getPaper().getId())) {// 没有考试记录且不被其他调卷规则引用
                     updatePaperInUse(paper.getPaper().getId(), 0);
                 }
             }
@@ -428,7 +463,8 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
     @Override
     public boolean checkIsAllQbjectiveQuestion(String paperId) {
         // 优先从redis中获取缓存dto
-        PaperDto cachePaperDto = redisClient.get(CacheConstants.CACHE_Q_TEMP_PAPER + paperId, PaperDto.class, DEFAULT_TIME_OUT);
+        PaperDto cachePaperDto = redisClient.get(CacheConstants.CACHE_Q_TEMP_PAPER + paperId, PaperDto.class,
+                DEFAULT_TIME_OUT);
         if (cachePaperDto != null) {
             return cachePaperDto.isAllQbjectiveQuestion();
         }
@@ -491,7 +527,8 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
             String key = entry.getKey();
             // 根据原有试卷重新组卷得到新试卷
             /*
-             * Paper newPaper = this.recombinationPaper(entry.getValue(), PaperType.PREVIEW,
+             * Paper newPaper = this.recombinationPaper(entry.getValue(),
+             * PaperType.PREVIEW,
              * extractConfig.getScrambling_the_question_order(),
              * extractConfig.getScrambling_the_option_order());
              */
@@ -503,9 +540,12 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
     /**
      * 重组试卷,生成新的试卷
      *
-     * @param paper              选中的试卷
-     * @param upSetQuestionOrder 客观题小题乱序 1:乱序 0:不乱序
-     * @param upSetOptionOrder   客观题选项乱序 1:乱序 0:不乱序
+     * @param paper
+     *            选中的试卷
+     * @param upSetQuestionOrder
+     *            客观题小题乱序 1:乱序 0:不乱序
+     * @param upSetOptionOrder
+     *            客观题选项乱序 1:乱序 0:不乱序
      * @return
      */
     public Paper recombinationPaper(Paper paper, PaperType paperType, int upSetQuestionOrder, int upSetOptionOrder) {
@@ -945,7 +985,7 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
      */
     @Override
     public QuestionDto extractExamQuestion(String examId, String courseCode, String groupCode,
-                                           String paperDetailUnitId) {
+            String paperDetailUnitId) {
         PaperDetailUnit paperDetailUnit = Model.of(paperDetailUnitRepo.findById(paperDetailUnitId));
         List<PaperDetailUnit> paperDetailUnits = new ArrayList<>();
         paperDetailUnits.add(paperDetailUnit);
@@ -1116,7 +1156,7 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
 
     @Override
     public Page<ExtractConfig> findPageExtractConfig(int currentPage, int pageSize, Long examId, String courseCode,
-                                                     String orgId, UserDataRule ud) {
+            String orgId, UserDataRule ud) {
         if (ud.assertEmptyQueryResult()) {
             return Page.empty();
         }
@@ -1402,7 +1442,7 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
         GetExamCourseListReq req = new GetExamCourseListReq();
         req.setExamId(examRes.getExamBean().getId());
         Long start = 1L;
-        for (; ; ) {
+        for (;;) {
             req.setStart(start);
             GetExamCourseListResp resp = examCloudService.getExamCourseList(req);
             courseInfoList.addAll(resp.getRelationList());
@@ -1425,11 +1465,13 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
     private void createByCourse(Long rootOrgId, ExamBean examBean, ExamCourseRelationBean course) {
         List<RandomPaperListVo> rps = randomPaperService.getList(rootOrgId, course.getCourseId());
         if (CollectionUtils.isEmpty(rps)) {
-            LOG.warn("==>没有启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, examBean.getId(), course.getCourseCode());
+            LOG.warn("==>没有启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, examBean.getId(),
+                    course.getCourseCode());
             throw new StatusException(course.getCourseName() + ",没有启用的随机抽题模板");
         }
         if (rps.size() > 1) {
-            LOG.warn("==>存在多个启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, examBean.getId(), course.getCourseCode());
+            LOG.warn("==>存在多个启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, examBean.getId(),
+                    course.getCourseCode());
             throw new StatusException("存在多个启用的随机抽题模板");
         }
 
@@ -1448,7 +1490,8 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
         extractConfig.setCallType(CallType.RANDOM_PAPER);
         extractConfig.setPlayTime(1);
         extractConfigRepo.save(extractConfig);
-        LOG.warn("==>创建随机抽卷调卷规则成功!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, examBean.getId(), course.getCourseCode());
+        LOG.warn("==>创建随机抽卷调卷规则成功!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, examBean.getId(),
+                course.getCourseCode());
 
         // 清除缓存
         extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
@@ -1499,16 +1542,19 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
         GetExamCourseResp settingResp = examCloudService.getExamCourseSetting(settingReq);
 
         if (settingResp.getBean() == null) {
-            LOG.warn("-->当前考试课程不存在!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(), req.getCourseCode());
+            LOG.warn("-->当前考试课程不存在!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(),
+                    req.getCourseCode());
             throw new StatusException("请先同步考生后再创建相应课程的调卷规则");
         }
         List<RandomPaperListVo> rps = randomPaperService.getList(rootOrgId, Long.valueOf(course.getId()));
         if (CollectionUtils.isEmpty(rps)) {
-            LOG.warn("-->没有启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(), req.getCourseCode());
+            LOG.warn("-->没有启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(),
+                    req.getCourseCode());
             throw new StatusException("没有启用的随机抽题模板");
         }
         if (rps.size() > 1) {
-            LOG.warn("-->存在多个启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(), req.getCourseCode());
+            LOG.warn("-->存在多个启用的随机抽题模板!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(),
+                    req.getCourseCode());
             throw new StatusException("存在多个启用的随机抽题模板");
         }
 
@@ -1527,10 +1573,79 @@ public class ExtractConfigServiceImpl implements ExtractConfigService {
         extractConfig.setRandomPaperId(rps.get(0).getId());
         extractConfig.setPlayTime(1);
         extractConfigRepo.save(extractConfig);
-        LOG.warn("-->创建随机抽卷调卷规则成功!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(), req.getCourseCode());
+        LOG.warn("-->创建随机抽卷调卷规则成功!rootOrgId:{} examId:{} courseCode:{}", rootOrgId, req.getExamId(),
+                req.getCourseCode());
 
         // 清除缓存
         extractConfigCache.remove(extractConfig.getExamId(), extractConfig.getCourseCode());
     }
 
+    @Override
+    public List<ExtractConfigBean> getExtractConfigList(Long examId) {
+        if (examId == null) {
+            throw new StatusException("考试id不能为空");
+        }
+        List<ExtractConfig> ecs = findConfigListByExam(examId);
+        if (CollectionUtils.isEmpty(ecs)) {
+            throw new StatusException("该考试调卷规则未制定");
+        }
+        List<ExtractConfigBean> ret = new ArrayList<>();
+        for (ExtractConfig extractConfig : ecs) {
+            if (CallType.WHOLE_SET.equals(extractConfig.getCallType())) {
+                if (CollectionUtils.isEmpty(extractConfig.getExamPaperList())) {
+                    throw new StatusException("可供抽取的试卷集合为空!");
+                }
+            }
+
+            ExtractConfigBean cacheBean = new ExtractConfigBean();
+            ret.add(cacheBean);
+            cacheBean.setCourseId(Long.valueOf(extractConfig.getCourse().getId()));
+            cacheBean.setId(extractConfig.getId());
+            cacheBean.setExamId(examId);
+            cacheBean.setCallType(extractConfig.getCallType());
+            cacheBean.setCourseCode(extractConfig.getCourseCode());
+            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<ExtractConfigDetailBean> details = new ArrayList<>();
+                for (ExamPaper examPaper : extractConfig.getExamPaperList()) {
+                    details.add(new ExtractConfigDetailBean(examPaper.getGroupCode(), examPaper.getPaper().getId(),
+                            examPaper.getWeight()));
+                }
+                cacheBean.setDetails(details);
+            }
+        }
+        return ret;
+    }
+
+    private boolean getByShort(Short s) {
+        if (s == null) {
+            return false;
+        } else {
+            return s != 0;
+        }
+    }
+
+    private List<ExtractConfig> findConfigListByExam(Long examId) {
+        if (examId == null) {
+            return null;
+        }
+
+        Query query = new Query();
+        query.addCriteria(Criteria.where("examId").is(examId));
+
+        List<ExtractConfig> list = this.mongoTemplate.find(query, ExtractConfig.class);
+
+        if (CollectionUtils.isNotEmpty(list)) {
+            for (ExtractConfig ec : list) {
+                if (ec != null && ec.getCallType() == null) {
+                    ec.setCallType(CallType.WHOLE_SET);
+                }
+            }
+        }
+        return list;
+    }
 }

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

@@ -29,6 +29,7 @@ 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.core.questions.service.util.RandomNumberUtil;
 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;
@@ -65,1241 +66,1246 @@ 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;
-					Integer anyCount = 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();
-							anyCount=anyCount+getIntVal(unitStruct.getPublicAnyDifficulty())
-							+getIntVal(unitStruct.getNoPublicAnyDifficulty());
-						}
-					}
-					sqinfo.setHardInfo(new StructQuestionCountInfo(difficultyCount, true));
-					sqinfo.setMediumInfo(new StructQuestionCountInfo(mediumCount, true));
-					sqinfo.setEasyInfo(new StructQuestionCountInfo(simpleCount, true));
-					sqinfo.setAnyInfo(new StructQuestionCountInfo(anyCount, true));
-				}
-			}
-		}
-		return ret;
-	}
-	
-	private int getIntVal(Integer val) {
-		if(val==null) {
-			return 0;
-		}
-		return val;
-	}
-
-	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()) {
-						if((unitStruct.getNoPublicAnyDifficulty()!=null&&unitStruct.getNoPublicAnyDifficulty()>0)
-								||(unitStruct.getPublicAnyDifficulty()!=null&&unitStruct.getPublicAnyDifficulty()>0)) {
-							return null;
-						}
-						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().addQuesName(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().addQuesName(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()+sqinfo.getAnyInfo().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);
-			}
-			if (!si.getAnyInfo().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);
-			}
-			if (!si.getAnyInfo().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<>());
-			}
-			for (RandomPaperQuestionDto dto : si.getAnyInfo().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);
-		}
-		//不限难度的一定要在最后抽题
-		if (getIntVal(us.getPublicAnyDifficulty()) > 0) {
-			cd.setSi(sqinfo.getAnyInfo());
-			cd.setPub(true);
-			cd.setDifficulty(QuestionDifficulty.ANY.getName());
-			cd.setNeedCount(us.getPublicAnyDifficulty());
-			setQuestionInfoByExactItem(cd);
-		}
-		if (getIntVal(us.getNoPublicAnyDifficulty()) > 0) {
-			cd.setSi(sqinfo.getAnyInfo());
-			cd.setPub(false);
-			cd.setDifficulty(QuestionDifficulty.ANY.getName());
-			cd.setNeedCount(us.getNoPublicAnyDifficulty());
-			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 checkHas(List<String> list,Set<String> set) {
-		if(list==null||set==null) {
-			return false;
-		}
-		if(list.size()==0||set.size()==0) {
-			return false;
-		}
-		for(String s:list) {
-			if(set.contains(s)) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	private boolean checkExactQuesType(List<String> quesNames, QuesStructType st, Boolean pub, String difficulty,
-			QuestionDto question) {
-		if (CollectionUtils.isNotEmpty(quesNames)) {
-			if (checkHas(quesNames, question.getQuesName())&& st.equals(question.getQuestionType())) {
-				if(QuestionDifficulty.ANY.getName().equals(difficulty)) {
-					if (question.getPublicity().equals(pub)) {
-						return true;
-					}
-				}else {
-					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 (checkHas(quesNames, 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);
-				}
-				for (RandomPaperQuestionDto dto : si.getAnyInfo().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);
-		}
-		//不限难度的一定要在最后抽题
-		if (getIntVal(us.getPublicAnyDifficulty()) > 0) {
-			param.setUnitCount(us.getPublicAnyDifficulty());
-			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
-					+ QuestionDifficulty.ANY.getName();
-			param.setKey(key);
-			createUnit(param);
-		}
-		if (getIntVal(us.getNoPublicAnyDifficulty()) > 0) {
-			param.setUnitCount(us.getNoPublicAnyDifficulty());
-			String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
-					+ QuestionDifficulty.ANY.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;
-	}
-
-	@Override
-	public List<RandomPaperListVo> getList(Long rootOrgId, Long courseId) {
-		List<Criteria> cs = new ArrayList<>();
-		cs.add(Criteria.where("rootOrgId").is(rootOrgId));
-
-		cs.add(Criteria.where("enable").is(true));
-
-		cs.add(Criteria.where("courseId").is(courseId));
-		Criteria and = new Criteria();
-		Criteria[] cas = new Criteria[cs.size()];
-		and.andOperator(cs.toArray(cas));
-		Query query = Query.query(and);
-
-		List<RandomPaperListVo> paperList = this.mongoTemplate.find(query, RandomPaperListVo.class, "randomPaper");
-
-		return paperList;
-	}
+    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;
+                    Integer anyCount = 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();
+                            anyCount = anyCount + getIntVal(unitStruct.getPublicAnyDifficulty())
+                                    + getIntVal(unitStruct.getNoPublicAnyDifficulty());
+                        }
+                    }
+                    sqinfo.setHardInfo(new StructQuestionCountInfo(difficultyCount, true));
+                    sqinfo.setMediumInfo(new StructQuestionCountInfo(mediumCount, true));
+                    sqinfo.setEasyInfo(new StructQuestionCountInfo(simpleCount, true));
+                    sqinfo.setAnyInfo(new StructQuestionCountInfo(anyCount, true));
+                }
+            }
+        }
+        return ret;
+    }
+
+    private int getIntVal(Integer val) {
+        if (val == null) {
+            return 0;
+        }
+        return val;
+    }
+
+    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()) {
+                        if ((unitStruct.getNoPublicAnyDifficulty() != null && unitStruct.getNoPublicAnyDifficulty() > 0)
+                                || (unitStruct.getPublicAnyDifficulty() != null
+                                        && unitStruct.getPublicAnyDifficulty() > 0)) {
+                            return null;
+                        }
+                        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().addQuesName(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().addQuesName(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() + sqinfo.getAnyInfo().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);
+            }
+            if (!si.getAnyInfo().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);
+            }
+            if (!si.getAnyInfo().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<>());
+            }
+            for (RandomPaperQuestionDto dto : si.getAnyInfo().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);
+        }
+        // 不限难度的一定要在最后抽题
+        if (getIntVal(us.getPublicAnyDifficulty()) > 0) {
+            cd.setSi(sqinfo.getAnyInfo());
+            cd.setPub(true);
+            cd.setDifficulty(QuestionDifficulty.ANY.getName());
+            cd.setNeedCount(us.getPublicAnyDifficulty());
+            setQuestionInfoByExactItem(cd);
+        }
+        if (getIntVal(us.getNoPublicAnyDifficulty()) > 0) {
+            cd.setSi(sqinfo.getAnyInfo());
+            cd.setPub(false);
+            cd.setDifficulty(QuestionDifficulty.ANY.getName());
+            cd.setNeedCount(us.getNoPublicAnyDifficulty());
+            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 checkHas(List<String> list, Set<String> set) {
+        if (list == null || set == null) {
+            return false;
+        }
+        if (list.size() == 0 || set.size() == 0) {
+            return false;
+        }
+        for (String s : list) {
+            if (set.contains(s)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean checkExactQuesType(List<String> quesNames, QuesStructType st, Boolean pub, String difficulty,
+            QuestionDto question) {
+        if (CollectionUtils.isNotEmpty(quesNames)) {
+            if (checkHas(quesNames, question.getQuesName()) && st.equals(question.getQuestionType())) {
+                if (QuestionDifficulty.ANY.getName().equals(difficulty)) {
+                    if (question.getPublicity().equals(pub)) {
+                        return true;
+                    }
+                } else {
+                    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 (checkHas(quesNames, 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);
+                }
+                for (RandomPaperQuestionDto dto : si.getAnyInfo().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);
+        }
+        // 不限难度的一定要在最后抽题
+        if (getIntVal(us.getPublicAnyDifficulty()) > 0) {
+            param.setUnitCount(us.getPublicAnyDifficulty());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + true + "-"
+                    + QuestionDifficulty.ANY.getName();
+            param.setKey(key);
+            createUnit(param);
+        }
+        if (getIntVal(us.getNoPublicAnyDifficulty()) > 0) {
+            param.setUnitCount(us.getNoPublicAnyDifficulty());
+            String key = param.getDetailNumber() + "-" + param.getIndex() + "-" + false + "-"
+                    + QuestionDifficulty.ANY.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());
+
+        // 获取[0,rpqs.size() - 1]范围内的param.getUnitCount()个随机整数数组,作为获取rpqs元素的下标
+        int[] indexs = RandomNumberUtil.randomTake(rpqs.size() - 1, param.getUnitCount());
+
+        for (int i = 0; i < param.getUnitCount(); i++) {
+            RandomPaperQuestion rpq = rpqs.get(indexs[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;
+    }
+
+    @Override
+    public List<RandomPaperListVo> getList(Long rootOrgId, Long courseId) {
+        List<Criteria> cs = new ArrayList<>();
+        cs.add(Criteria.where("rootOrgId").is(rootOrgId));
+
+        cs.add(Criteria.where("enable").is(true));
+
+        cs.add(Criteria.where("courseId").is(courseId));
+        Criteria and = new Criteria();
+        Criteria[] cas = new Criteria[cs.size()];
+        and.andOperator(cs.toArray(cas));
+        Query query = Query.query(and);
+
+        List<RandomPaperListVo> paperList = this.mongoTemplate.find(query, RandomPaperListVo.class, "randomPaper");
+
+        return paperList;
+    }
 }

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

@@ -0,0 +1,50 @@
+package cn.com.qmth.examcloud.core.questions.service.util;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class RandomNumberUtil {
+
+    /**
+     * 获取[0,maxNum]的takeCount个数字
+     * 
+     * @param maxNum
+     *            随机获取的最大数字
+     * @param takeCount
+     *            随机获取的数量
+     */
+    public static int[] randomTake(int maxNum, int takeCount) {
+        if (takeCount <= 0) {
+            throw new RuntimeException("获取数量必须大于0");
+        }
+        if (maxNum < 0) {
+            throw new RuntimeException("最大数字不能小于0");
+        }
+        if (maxNum + 1 < takeCount) {
+            throw new RuntimeException("最大数字+1不能小于获取数量");
+        }
+
+        // 0到maxNum的全量数字
+        int[] indexs = new int[maxNum + 1];
+
+        for (int i = 0; i <= maxNum; i++) {
+            indexs[i] = i;
+        }
+
+        // 结果集存放
+        int[] ret = new int[takeCount];
+
+        for (int i = 0; i < takeCount; i++) {
+            // 获取随机数,范围[i,maxNum + 1),作为indexs数组的下标
+            int randomIndex = ThreadLocalRandom.current().nextInt(i, maxNum + 1);
+            // 存入结果集
+            ret[i] = indexs[randomIndex];
+            // 和第i个元素交换获取的随机元素,下一轮获取随机数的下标就排除了第i个元素,也就是交换之后的randomIndex
+            int temp = indexs[i];
+            indexs[i] = indexs[randomIndex];
+            indexs[randomIndex] = temp;
+        }
+
+        return ret;
+    }
+
+}