Explorar el Código

fixbug随机抽卷,多线程乱序数组导致重复题

xiatian hace 1 mes
padre
commit
36fa5445d4

+ 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;
+    }
 }

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

@@ -0,0 +1,49 @@
+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 = indexs[ThreadLocalRandom.current().nextInt(i, maxNum + 1)];
+            // 存入结果集
+            ret[i] = randomIndex;
+            // 和第i个元素交换获取的随机元素,下一轮获取随机数的下标就排除了第i个元素,也就是交换之后的randomIndex
+            int temp = indexs[i];
+            indexs[i] = indexs[randomIndex];
+            indexs[randomIndex] = temp;
+        }
+
+        return ret;
+    }
+}