package com.mindskip.xzs.controller.admin; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.enums.CellExtraTypeEnum; import com.alibaba.excel.metadata.data.HyperlinkData; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import com.alibaba.fastjson.JSON; import com.mindskip.xzs.base.BaseApiController; import com.mindskip.xzs.base.RestResponse; import com.mindskip.xzs.base.SystemCode; import com.mindskip.xzs.context.WebContext; import com.mindskip.xzs.domain.Question; import com.mindskip.xzs.domain.QuestionSubject; import com.mindskip.xzs.domain.Subject; import com.mindskip.xzs.domain.TextContent; import com.mindskip.xzs.domain.enums.QuestionSourceEnum; import com.mindskip.xzs.domain.enums.QuestionStatusEnum; import com.mindskip.xzs.domain.enums.QuestionTypeEnum; import com.mindskip.xzs.domain.question.QuestionItemObject; import com.mindskip.xzs.domain.question.QuestionObject; import com.mindskip.xzs.domain.vo.DeptQuestionVO; import com.mindskip.xzs.excel.*; import com.mindskip.xzs.repository.DepartmentMapper; import com.mindskip.xzs.repository.DeptQuestionMapper; import com.mindskip.xzs.repository.SubjectMapper; import com.mindskip.xzs.service.*; import com.mindskip.xzs.utility.*; import com.mindskip.xzs.utility.convert.QuestionClassConvert; import com.mindskip.xzs.utility.excel.ExcelUtils; import com.mindskip.xzs.viewmodel.admin.education.SubjectPageRequestVM; import com.mindskip.xzs.viewmodel.admin.question.*; import com.github.pagehelper.PageInfo; import com.mindskip.xzs.vo.QuestionExportVO; import com.mindskip.xzs.vo.QuestionImportVO; import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.usermodel.DataValidationHelper; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.URLEncoder; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; @RestController("AdminQuestionController") @RequestMapping(value = "/api/admin/question") public class QuestionController extends BaseApiController { private final QuestionService questionService; private final TextContentService textContentService; private final SubjectMapper subjectMapper; private final DepartmentMapper departmentMapper; private final QuestionSubjectService questionSubjectService; private final DeptQuestionMapper deptQuestionMapper; private static final String SPLIT = "、"; public QuestionController(QuestionService questionService, TextContentService textContentService, SubjectMapper subjectMapper, DepartmentMapper departmentMapper, QuestionSubjectService questionSubjectService, DeptQuestionMapper deptQuestionMapper) { this.questionService = questionService; this.textContentService = textContentService; this.subjectMapper = subjectMapper; this.departmentMapper = departmentMapper; this.questionSubjectService = questionSubjectService; this.deptQuestionMapper = deptQuestionMapper; } @RequestMapping(value = "/page", method = RequestMethod.POST) public RestResponse> pageList(@RequestBody @Valid QuestionPageRequestVM model) { PageInfo pageInfo = questionService.page(model); PageInfo page = PageInfoHelper.copyMap(pageInfo, q -> { QuestionResponseVM vm = modelMapper.map(q, QuestionResponseVM.class); vm.setCreateTime(DateTimeUtil.dateFormat(q.getCreateTime())); vm.setScore(ExamUtil.scoreToVM(q.getScore())); TextContent textContent = textContentService.selectById(q.getInfoTextContentId()); QuestionObject questionObject = JsonUtil.toJsonObject(textContent.getContent(), QuestionObject.class); String clearHtml = HtmlUtil.clear(questionObject.getTitleContent()); vm.setShortTitle(clearHtml); vm.setQuestionSubjects(questionSubjectService.getQuestion(vm.getId()).stream().map(e -> { SubjectPageRequestVM subjectPageRequestVM = new SubjectPageRequestVM(); subjectPageRequestVM.setId(e.getSubjectId()); e.setSubName(subjectMapper.page(subjectPageRequestVM).get(0).getName()); return e; }).collect(Collectors.toList())); // 查询题目所属部门 String deptNames = deptQuestionMapper.deptByQuestionId(q.getId()).stream().map(DeptQuestionVO::getDeptName).collect(Collectors.joining("、")); vm.setDeptNames(deptNames); return vm; }); return RestResponse.ok(page); } @RequestMapping(value = "/edit", method = RequestMethod.POST) public RestResponse edit(@RequestBody @Valid QuestionEditRequestVM model) { RestResponse validQuestionEditRequestResult = validQuestionEditRequestVM(model); if (validQuestionEditRequestResult.getCode() != SystemCode.OK.getCode()) { return validQuestionEditRequestResult; } if (null == model.getId()) { questionService.insertFullQuestion(model, getCurrentUser().getId()); } else { questionService.updateFullQuestion(model); } return RestResponse.ok(); } @RequestMapping(value = "/select/{id}", method = RequestMethod.POST) public RestResponse select(@PathVariable Integer id) { return RestResponse.ok(questionService.getQuestionEditRequestVM(id)); } @RequestMapping(value = "/delete/{id}", method = RequestMethod.POST) public RestResponse delete(@PathVariable Integer id) { return RestResponse.ok(questionService.remove(id)); } private RestResponse validQuestionEditRequestVM(QuestionEditRequestVM model) { int qType = model.getQuestionType().intValue(); boolean requireCorrect = qType == QuestionTypeEnum.SingleChoice.getCode() || qType == QuestionTypeEnum.TrueFalse.getCode(); if (requireCorrect) { if (StringUtils.isBlank(model.getCorrect())) { String errorMsg = ErrorUtil.parameterErrorFormat("correct", "不能为空"); return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg); } } if (qType == QuestionTypeEnum.GapFilling.getCode()) { Integer fillSumScore = model.getItems().stream().mapToInt(d -> ExamUtil.scoreFromVM(d.getScore())).sum(); Integer questionScore = ExamUtil.scoreFromVM(model.getScore()); if (!fillSumScore.equals(questionScore)) { String errorMsg = ErrorUtil.parameterErrorFormat("score", "空分数和与题目总分不相等"); return new RestResponse<>(SystemCode.ParameterValidError.getCode(), errorMsg); } } return RestResponse.ok(); } /** * 下载题目导入模板 * * @param response * @throws IOException */ @GetMapping("/download/question/import/temp") public void getImportTemp(HttpServletResponse response) throws IOException { String fileName = URLEncoder.encode("题目导入模板", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 构建模板样例数据 List data = new ArrayList<>(4); QuestionImportVO questionImportVO = new QuestionImportVO(); questionImportVO.setQuestionType("单选题"); questionImportVO.setDifficult(2); questionImportVO.setCorrect("B"); questionImportVO.setScore(2); questionImportVO.setSubjectName("测试课目"); questionImportVO.setAnalyze("B是对的"); questionImportVO.setTitle("这是一道测试题目,使用该模板请删除或替换这道题"); questionImportVO.setOptionName("A"); questionImportVO.setOptionValue("选我"); data.add(questionImportVO); QuestionImportVO questionImportVO1 = new QuestionImportVO(); questionImportVO1.setOptionName("B"); questionImportVO1.setOptionValue("选B"); data.add(questionImportVO1); QuestionImportVO questionImportVO2 = new QuestionImportVO(); questionImportVO2.setOptionName("C"); questionImportVO2.setOptionValue("选C"); data.add(questionImportVO2); QuestionImportVO questionImportVO3 = new QuestionImportVO(); questionImportVO3.setOptionName("D"); questionImportVO3.setOptionValue("选D"); data.add(questionImportVO3); // 查出所有的课目(excel下拉数据) List subjects = subjectMapper.allSubject(new ArrayList<>()); List subjectNameList = subjects.stream().map(Subject::getName).collect(Collectors.toList()); EasyExcel.write(response.getOutputStream(), QuestionImportVO.class) .sheet("模板") .registerWriteHandler(new SelectExcel(subjectNameList)) .registerWriteHandler(new FixedMergeCellStrategy(2, 4, Arrays.asList(0, 1, 2, 5, 6, 7, 8))) .doWrite(data); } @PostMapping("/question/export") public void exportQuestion(@RequestBody QuestionExportVO query, HttpServletResponse response) throws IOException { // 查询导出数据 List exportData = questionService.export(query); // 构建数据 List exportList = new ArrayList<>(exportData.size() * 4); // 行合并规则 List mergeRowList = new ArrayList<>(exportData.size()); int j = 2; for (QuestionImportVO data : exportData) { QuestionObject questionContent = JSON.parseObject(data.getQuestionContent(), QuestionObject.class); RowItem rowItem = new RowItem(); rowItem.setStart(j); int end = j + questionContent.getQuestionItemObjects().size() - 1; rowItem.setEnd(end); mergeRowList.add(rowItem); j = end + 1; int i = 0; for (QuestionItemObject option : questionContent.getQuestionItemObjects()) { if (i == 0) { QuestionImportVO master = new QuestionImportVO(); BeanUtils.copyProperties(data, master); if (org.springframework.util.StringUtils.hasText(data.getQuestionType())) { master.setQuestionType(QuestionTypeEnum.fromCode(Integer.valueOf(data.getQuestionType())).getName()); } master.setOptionName(option.getPrefix()); master.setOptionValue(option.getContent()); master.setTitle(questionContent.getTitleContent()); master.setAnalyze(questionContent.getAnalyze()); master.setSubjectName(data.getSubjectList().stream().collect(Collectors.joining("、"))); master.setCorrect(data.getCorrect().replaceAll(",", "、")); BigDecimal score = BigDecimal.valueOf(master.getScore()); master.setScore(score.divide(BigDecimal.TEN).intValue()); exportList.add(master); } else { QuestionImportVO optionItem = new QuestionImportVO(); optionItem.setOptionName(option.getPrefix()); optionItem.setOptionValue(option.getContent()); exportList.add(optionItem); } i++; } } response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("题目导出数据", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 查出所有的课目(excel下拉数据) List subjects = subjectMapper.allSubject(new ArrayList<>()); List subjectNameList = subjects.stream().map(Subject::getName).collect(Collectors.toList()); EasyExcel.write(response.getOutputStream(), QuestionImportVO.class) .sheet("题目导出数据") .registerWriteHandler(new SelectExcel(subjectNameList)) .registerWriteHandler(new DynamicMergeCellStrategy(mergeRowList, Arrays.asList(0, 1, 2, 5, 6, 7, 8))) .doWrite(exportList); } /** * 题目导入 * * easyexcel导入一对多,比如一个题目,四个选项。那么读取到的数据就有四条,第一条数据是齐全的。后面三条只有选项有值 * @param file * @throws IOException */ @PostMapping("/question/import") @Transactional(rollbackFor = Exception.class) public RestResponse importQuestion(@RequestPart("file") MultipartFile file) throws IOException { Consumer> consumer = (data) -> { // 题目的课目信息 List questionSubjectsList = new ArrayList<>(48); for (int i = 0; i < data.size(); i++) { // 题目实体 Question question = new Question(); // 读取的题目 QuestionImportVO excelQuestion = data.get(i); // 如果是第一条完整数据,那么继续往后读取选项内容 if (excelQuestion.master()) { // 该题的选项 List options = new ArrayList<>(4); // 选项内容 QuestionItemObject option = new QuestionItemObject(); option.setPrefix(excelQuestion.getOptionName()); option.setContent(excelQuestion.getOptionValue()); options.add(option); int next = 1; // 继续往后读选项 while (Boolean.TRUE) { // 判断是否是最后一条 if (next + i == data.size()) { break; } QuestionImportVO nextQuestion = data.get(next + i); if (nextQuestion.master()) { break; } QuestionItemObject nextOption = new QuestionItemObject(); nextOption.setPrefix(nextQuestion.getOptionName()); nextOption.setContent(nextQuestion.getOptionValue()); options.add(nextOption); next++; } i += next; // 保存题目内容 QuestionObject questionObject = new QuestionObject(); questionObject.setQuestionItemObjects(options); questionObject.setAnalyze(excelQuestion.getAnalyze()); questionObject.setTitleContent(excelQuestion.getTitle()); questionObject.setCorrect(excelQuestion.getCorrect()); TextContent textContent = new TextContent(); textContent.setContent(JSON.toJSONString(questionObject)); textContent.setCreateTime(new Date()); textContentService.insert(textContent); // 保存题目信息 // 设置题型 question.setQuestionType(QuestionTypeEnum.get(excelQuestion.getQuestionType())); // 答案(多选需要用,分割保存字符串到数据库) String[] corrects = excelQuestion.getCorrect().split(SPLIT); if (corrects.length > 1) { question.setCorrect(Arrays.asList(corrects).stream().collect(Collectors.joining(","))); } else { question.setCorrect(excelQuestion.getCorrect()); } // 难度 question.setDifficult(excelQuestion.getDifficult()); // 分数 question.setScore(ExamUtil.scoreFromVM(String.valueOf(excelQuestion.getScore()))); // 创建人 question.setCreateUser(1); question.setStatus(QuestionStatusEnum.OK.getCode()); question.setCreateTime(new Date()); question.setDeleted(Boolean.FALSE); question.setInfoTextContentId(textContent.getId()); questionService.insert(question); // 查出所有的课目 List subjects = subjectMapper.allSubject(new ArrayList<>()); List subjectNames = Arrays.asList(excelQuestion.getSubjectName().split(SPLIT)); List targetSubject = subjects.stream() .filter(subject -> subjectNames.contains(subject.getName())) .collect(Collectors.toList()); if (CollectionUtils.isEmpty(targetSubject)) { // todo 记录这个错误 continue; } // 构建课目-题目信息 questionSubjectsList = targetSubject.stream().map(subject -> { QuestionSubject questionSubject = new QuestionSubject(); questionSubject.setQuestionId(question.getId()); questionSubject.setSubjectId(subject.getId()); questionSubject.setDeleted(0); return questionSubject; }).collect(Collectors.toList()); } System.out.println(question); } // 批量保存题目-课目信息 if (! CollectionUtils.isEmpty(questionSubjectsList)) { questionSubjectService.saves(questionSubjectsList); } }; EasyExcel.read(file.getInputStream(), QuestionImportVO.class, new CurrencyDataListener(consumer)).sheet("模板").doRead(); return RestResponse.ok(); } @PostMapping("/import") public RestResponse importUser(@RequestPart("file") MultipartFile file) throws Exception { List questionEditVOS = ExcelUtils.readMultipartFile(file, QuestionEditVO.class) .stream().map(e -> { e.setQuestionType(QuestionTypeEnum.get(e.getType())); // e.setSubjectId(subjectMapper.getName(e.getSbNames()).getId()); // e.setGradeLevel(departmentMapper.getName(e.getDepartment()).getId()); return e; }).collect(Collectors.toList()); List list = QuestionClassConvert.INSTANCE.QuestionEditVOListToQuestionEditRequestVMList(questionEditVOS); List questionEditItemVMS = new ArrayList<>(); QuestionEditItemVM questionEditItemVM = new QuestionEditItemVM(); //组装题目 for (QuestionEditRequestVM vm : list) { questionEditItemVM.setPrefix("A"); questionEditItemVM.setContent(vm.getA()); questionEditItemVMS.add(questionEditItemVM); questionEditItemVM = new QuestionEditItemVM(); questionEditItemVM.setPrefix("B"); questionEditItemVM.setContent(vm.getB()); questionEditItemVMS.add(questionEditItemVM); if(!"".equals(vm.getC()) && vm.getC()!=null){ questionEditItemVM = new QuestionEditItemVM(); questionEditItemVM.setPrefix("C"); questionEditItemVM.setContent(vm.getC()); questionEditItemVMS.add(questionEditItemVM); } if(!"".equals(vm.getD()) && vm.getD()!=null){ questionEditItemVM = new QuestionEditItemVM(); questionEditItemVM.setPrefix("D"); questionEditItemVM.setContent(vm.getD()); questionEditItemVMS.add(questionEditItemVM); } vm.setItems(questionEditItemVMS); List str = Arrays.asList(vm.getCorrect().split(",")); List subjectList = subjectMapper.getNames(vm.getSbNames().split(",")); Integer[] arr =subjectList.stream() .map(Subject::getId).toArray(Integer[]::new); vm.setSubjectIds(arr); //多选 if (str.size() > 1) { vm.setCorrectArray(str); vm.setCorrect(null); } vm.setScore(QuestionSourceEnum.fromType(vm.getQuestionType()).toString()); vm.setDifficult(4); questionService.insertFullQuestion(vm, getCurrentUser().getId()); questionEditItemVMS.clear(); } return RestResponse.ok(); } }