src/main/java/com/ycl/jxkg/controller/admin/ExamPaperController.java
@@ -1,26 +1,30 @@ package com.ycl.jxkg.controller.admin; import com.alibaba.fastjson.JSON; import com.github.pagehelper.PageInfo; import com.ycl.jxkg.base.BaseApiController; import com.ycl.jxkg.base.Result; import com.ycl.jxkg.domain.entity.ExamPaper; import com.ycl.jxkg.domain.entity.ExamPaperQuestion; import com.ycl.jxkg.domain.form.ExamPaperForm; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperEditRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperPageRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamResponseVO; import com.ycl.jxkg.domain.vo.admin.exam.QuestionExportVO; import com.ycl.jxkg.enums.VisibilityEnum; import com.ycl.jxkg.group.Add; import com.ycl.jxkg.group.Update; import com.ycl.jxkg.mapper.ExamPaperMapper; import com.ycl.jxkg.service.ExamPaperQuestionService; import com.ycl.jxkg.service.ExamPaperService; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperPageRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperEditRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamResponseVO; import com.github.pagehelper.PageInfo; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.util.Date; import java.util.List; @@ -88,4 +92,20 @@ List<ExamPaper> list = examPaperService.myExamPaper(paperType); return Result.ok(list); } @GetMapping("/download/importTemplate") public void importTemplate(HttpServletResponse response) { examPaperService.importTemplate(response); } @PostMapping("/export") public void export(@RequestBody QuestionExportVO query, HttpServletResponse response) { examPaperService.export(query, response); } @PostMapping("/import") public Result importPaper(@RequestParam("file") MultipartFile file, @RequestParam("examPaper") String form) { return examPaperService.importPaper(file, JSON.parseObject(form, ExamPaperForm.class)); } } src/main/java/com/ycl/jxkg/domain/vo/admin/exam/OptionAndValueVO.java
New file @@ -0,0 +1,20 @@ package com.ycl.jxkg.domain.vo.admin.exam; import com.alibaba.excel.annotation.ExcelProperty; import lombok.Data; /** * 题目选项 * * @author:xp * @date:2024/3/15 22:53 */ @Data public class OptionAndValueVO { @ExcelProperty({"题目选项", "选项"}) private String optionName; @ExcelProperty({"题目选项", "选项值"}) private String optionValue; } src/main/java/com/ycl/jxkg/domain/vo/admin/exam/QuestionExportData.java
New file @@ -0,0 +1,60 @@ package com.ycl.jxkg.domain.vo.admin.exam; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import lombok.Data; import java.util.List; /** * @author:xp * @date:2024/7/2 14:05 */ @Data public class QuestionExportData { @ExcelIgnore private Integer id; @ExcelIgnore private String content; @ExcelIgnore private List<String> subjectList; @ExcelProperty(value = "题型") private String questionType; @ExcelProperty(value = "课目") @ColumnWidth(30) private String subject; @ExcelProperty(value = "题干") @ColumnWidth(40) private String title; @ExcelProperty(value = "选项A") @ColumnWidth(40) private String optionA; @ColumnWidth(40) @ExcelProperty(value = "选项B") private String optionB; @ColumnWidth(40) @ExcelProperty(value = "选项C") private String optionC; @ColumnWidth(40) @ExcelProperty(value = "选项D") private String optionD; @ColumnWidth(120) @ExcelProperty(value = "解析") private String analyze; @ColumnWidth(10) @ExcelProperty(value = "答案") private String answer; } src/main/java/com/ycl/jxkg/domain/vo/admin/exam/QuestionExportVO.java
New file @@ -0,0 +1,18 @@ package com.ycl.jxkg.domain.vo.admin.exam; import lombok.Data; /** * @author:xp * @date:2024/3/28 16:40 */ @Data public class QuestionExportVO { /** * 试卷 */ private Integer examPaperId; } src/main/java/com/ycl/jxkg/domain/vo/admin/exam/QuestionImportVO.java
New file @@ -0,0 +1,79 @@ package com.ycl.jxkg.domain.vo.admin.exam; import com.alibaba.excel.annotation.ExcelIgnore; import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentStyle; import com.alibaba.excel.enums.poi.HorizontalAlignmentEnum; import com.alibaba.excel.enums.poi.VerticalAlignmentEnum; import lombok.Data; import org.springframework.util.StringUtils; import java.util.List; /** * @author gonghl * @since 2024-7-4 */ @Data @ColumnWidth(20) @ContentStyle(horizontalAlignment = HorizontalAlignmentEnum.CENTER, verticalAlignment = VerticalAlignmentEnum.CENTER) public class QuestionImportVO { @ExcelProperty("标题") private String label; @ExcelProperty("题目类型") private String questionType; @ExcelProperty("科目") private String subject; @ExcelIgnore private List<Integer> subjectIds; @ExcelIgnore private List<String> subjectList; @ColumnWidth(80) @ExcelProperty("题干") private String title; /** 题目内容 **/ @ExcelIgnore private String questionContent; /** 选项内容 **/ @ExcelProperty({"题目选项", "选项"}) private String optionName; @ExcelProperty({"题目选项", "选项值"}) private String optionValue; @ExcelProperty("答案(多个用、隔开)") private String correct; /** 解析 **/ @ColumnWidth(30) @ExcelProperty("解析") private String analyze; /** 题目分数 **/ @ExcelProperty("题目分数") private Integer score; /** 题目难度 **/ @ExcelProperty("题目难度") private Integer difficult; /** * 返回该条数据是不是题,因为还有选项。选项的这些值是空的 */ public boolean master() { return StringUtils.hasText(questionType) && StringUtils.hasText(label); } public boolean intact() { return StringUtils.hasText(title) && StringUtils.hasText(correct); } } src/main/java/com/ycl/jxkg/enums/QuestionTypeEnum.java
@@ -54,6 +54,17 @@ return name; } public static Integer get(String text){ if("".equals(text) || text == null){ return null; } QuestionTypeEnum[] enums = QuestionTypeEnum.values(); for (QuestionTypeEnum anEnum : enums) { if(anEnum.getName().equals(text)){ return anEnum.getCode(); } } return null; } } src/main/java/com/ycl/jxkg/excel/CurrencyDataListener.java
New file @@ -0,0 +1,105 @@ package com.ycl.jxkg.excel; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.metadata.data.ReadCellData; import com.alibaba.excel.read.listener.ReadListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Consumer; /** * easyExcel 多表通用读取监听器 * * @author xp */ public class CurrencyDataListener<T> implements ReadListener<T> { private Consumer consumer; /** * 每隔100条存储数据库,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 100; /** * 缓存的数据 */ private List<T> cachedDataList = new ArrayList<>(BATCH_COUNT); private final static Logger log = LoggerFactory.getLogger(CurrencyDataListener.class); public CurrencyDataListener(Consumer<List<T>> consumer) { this.consumer = consumer; } /** * 读取出现异常处理 * * @param e * @param analysisContext * @throws Exception */ @Override public void onException(Exception e, AnalysisContext analysisContext) throws Exception { } /** * 处理表头 * @param map * @param analysisContext */ @Override public void invokeHead(Map<Integer, ReadCellData<?>> map, AnalysisContext analysisContext) { } /** * 读取数据,每一条数据解析都会来调用 * * @param data * @param analysisContext */ @Override public void invoke(T data, AnalysisContext analysisContext) { cachedDataList.add(data); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (cachedDataList.size() >= BATCH_COUNT) { try { // 必须要捕获异常,否则列表不会清空 saveData(); } catch (Exception e) { e.printStackTrace(); } // 存储完成清理 list cachedDataList = new ArrayList<>(BATCH_COUNT); } } /** * 读取完成 * * @param analysisContext */ @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { saveData(); log.info("所有数据解析完成!"); } @Override public boolean hasNext(AnalysisContext analysisContext) { return true; } private void saveData() { log.info("{}条数据,开始存储数据库!", cachedDataList.size()); consumer.accept(cachedDataList); log.info("存储数据库成功!"); } } src/main/java/com/ycl/jxkg/excel/DynamicMergeCellStrategy.java
New file @@ -0,0 +1,44 @@ package com.ycl.jxkg.excel; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.merge.AbstractMergeStrategy; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; import java.util.List; /** * @author:xp * @date:2024/3/16 11:20 */ public class DynamicMergeCellStrategy extends AbstractMergeStrategy { /** * 多少行合并一次(起始位置) */ private List<RowItem> rowMergeList; /** * 哪些列需要合并行 */ private List<Integer> mergeWhichColumn; public DynamicMergeCellStrategy(List<RowItem> rowMergeList, List<Integer> mergeWhichColumn) { this.rowMergeList = rowMergeList; this.mergeWhichColumn = mergeWhichColumn; } @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { // 只有单元格的行列在合并范围内才合并 if (mergeWhichColumn.contains(cell.getColumnIndex())) { rowMergeList.stream().forEach(rowItem -> { if (rowItem.getStart() <= relativeRowIndex && rowItem.getEnd() >= relativeRowIndex) { CellRangeAddress cellRangeAddress = new CellRangeAddress(rowItem.getStart(), rowItem.getEnd(), cell.getColumnIndex(), cell.getColumnIndex()); sheet.addMergedRegionUnsafe(cellRangeAddress); } }); } } } src/main/java/com/ycl/jxkg/excel/FixedMergeCellStrategy.java
New file @@ -0,0 +1,45 @@ package com.ycl.jxkg.excel; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.write.merge.AbstractMergeStrategy; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.util.CellRangeAddress; import java.util.List; /** * @author:xp * @date:2024/3/16 11:20 */ public class FixedMergeCellStrategy extends AbstractMergeStrategy { /** * 起始位置 */ private Integer startRow; /** * 合并多少行 */ private Integer mergeRowNumber; /** * 哪些列需要合并行 */ private List<Integer> mergeWhichColumn; public FixedMergeCellStrategy(Integer startRow, Integer mergeRowNumber, List<Integer> mergeWhichColumn) { this.startRow = startRow; this.mergeRowNumber = mergeRowNumber; this.mergeWhichColumn = mergeWhichColumn; } @Override protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { for (Integer whichColumn : mergeWhichColumn) { CellRangeAddress cellRangeAddress = new CellRangeAddress(startRow, startRow + mergeRowNumber - 1, whichColumn, whichColumn); sheet.addMergedRegionUnsafe(cellRangeAddress); } } } src/main/java/com/ycl/jxkg/excel/RowItem.java
New file @@ -0,0 +1,16 @@ package com.ycl.jxkg.excel; import lombok.Data; /** * @author:xp * @date:2024/3/29 9:12 */ @Data public class RowItem { private Integer start; private Integer end; } src/main/java/com/ycl/jxkg/excel/SelectExcel.java
New file @@ -0,0 +1,77 @@ package com.ycl.jxkg.excel; import com.alibaba.excel.metadata.Head; import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.handler.CellWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddressList; import java.util.List; /** * 设置easy-excel下拉处理器 * * @author:xp * @date:2024/3/15 17:27 */ public class SelectExcel implements CellWriteHandler { /** * 题目类型 */ private String[] questionTypeList = {"单选题", "多选题", "判断题"}; /** * 课目 */ private List<String> subjectNameList; /** * 选项下拉数据 */ private String[] optionList = {"A","B","C","D","E","F","G","H"}; public SelectExcel(List subjectNameList) { this.subjectNameList = subjectNameList; } @Override public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) { // 获取sheet对象 Sheet sheet = writeSheetHolder.getSheet(); // 获取数据校验helper,excel的下拉就是通过数据校验设置 DataValidationHelper dataValidationHelper = sheet.getDataValidationHelper(); // 设置题目下拉范围,第一列,1500行内 CellRangeAddressList questionTypeRange = new CellRangeAddressList(1, 1500, 0, 0); // 添加题目下拉 DataValidationConstraint questionConstraint = dataValidationHelper.createExplicitListConstraint(questionTypeList); DataValidation questionValidation = dataValidationHelper.createValidation(questionConstraint, questionTypeRange); sheet.addValidationData(questionValidation); // 设置课目下拉范围,第2列,1500行内 CellRangeAddressList subjectTypeRange = new CellRangeAddressList(1, 1500, 1, 1); // 添加课目下拉 DataValidationConstraint subjectConstraint = dataValidationHelper.createExplicitListConstraint(subjectNameList.toArray((new String[0]))); DataValidation subjectValidation = dataValidationHelper.createValidation(subjectConstraint, subjectTypeRange); sheet.addValidationData(subjectValidation); // 设置选项下拉范围,第4列,1500行内 CellRangeAddressList optionRange = new CellRangeAddressList(2, 1500, 3, 3); // 添加选项下拉 DataValidationConstraint optionConstraint = dataValidationHelper.createExplicitListConstraint(optionList); DataValidation optionValidation = dataValidationHelper.createValidation(optionConstraint, optionRange); sheet.addValidationData(optionValidation); // 设置选项下拉范围,第4列,1500行内 CellRangeAddressList answerRange = new CellRangeAddressList(2, 1500, 5, 5); // 添加选项下拉 DataValidationConstraint answerConstraint = dataValidationHelper.createExplicitListConstraint(optionList); DataValidation answerValidation = dataValidationHelper.createValidation(answerConstraint, answerRange); sheet.addValidationData(answerValidation); } } src/main/java/com/ycl/jxkg/mapper/QuestionMapper.java
@@ -1,9 +1,11 @@ package com.ycl.jxkg.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ycl.jxkg.domain.other.KeyValue; import com.ycl.jxkg.domain.entity.Question; import com.ycl.jxkg.domain.other.KeyValue; import com.ycl.jxkg.domain.question.RandomQuestionDTO; import com.ycl.jxkg.domain.vo.admin.exam.QuestionExportVO; import com.ycl.jxkg.domain.vo.admin.exam.QuestionImportVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionPageRequestVO; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @@ -41,4 +43,7 @@ * @return */ List<Question> getRandomQuestion(@Param("subjectId") Integer subjectId, @Param("questionType") Integer questionType,@Param("difficult") Integer difficult, @Param("num") Integer num); List<QuestionImportVO> export(@Param("query") QuestionExportVO query); } src/main/java/com/ycl/jxkg/service/ExamPaperService.java
@@ -1,18 +1,20 @@ package com.ycl.jxkg.service; import com.baomidou.mybatisplus.extension.service.IService; import com.github.pagehelper.PageInfo; import com.ycl.jxkg.base.Result; import com.ycl.jxkg.domain.entity.ExamPaper; import com.ycl.jxkg.domain.entity.User; import com.ycl.jxkg.domain.form.ExamPaperForm; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperEditRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperPageRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamResponseVO; import com.ycl.jxkg.domain.vo.admin.exam.QuestionExportVO; import com.ycl.jxkg.domain.vo.student.dashboard.PaperFilter; import com.ycl.jxkg.domain.vo.student.dashboard.PaperInfo; import com.ycl.jxkg.domain.vo.student.exam.ExamPaperPageVO; import com.github.pagehelper.PageInfo; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.util.List; public interface ExamPaperService extends IService<ExamPaper> { @@ -45,4 +47,24 @@ Result addPaper(ExamPaperForm form); Result updateExamPaper(ExamPaperForm form); /** * 下载导入试卷模板 * @param response 响应 */ void importTemplate(HttpServletResponse response); /** * 导出试卷 * @param query 条件 * @param response 响应 */ void export(QuestionExportVO query, HttpServletResponse response); /** * 导入试卷 * @param file 文件 * @return 操作结果 */ Result importPaper(MultipartFile file, ExamPaperForm form); } src/main/java/com/ycl/jxkg/service/QuestionService.java
@@ -1,10 +1,12 @@ package com.ycl.jxkg.service; import com.baomidou.mybatisplus.extension.service.IService; import com.github.pagehelper.PageInfo; import com.ycl.jxkg.domain.entity.Question; import com.ycl.jxkg.domain.vo.admin.exam.QuestionExportVO; import com.ycl.jxkg.domain.vo.admin.exam.QuestionImportVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionEditRequestVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionPageRequestVO; import com.github.pagehelper.PageInfo; import com.ycl.jxkg.domain.vo.admin.question.QuestionResponseVO; import java.util.List; @@ -32,5 +34,11 @@ */ Boolean updateStatus(QuestionResponseVO question); /** * 题目导出数据 * @param query 查询条件 * @return 题目 */ List<QuestionImportVO> export(QuestionExportVO query); } src/main/java/com/ycl/jxkg/service/impl/ExamPaperServiceImpl.java
@@ -1,9 +1,9 @@ package com.ycl.jxkg.service.impl; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson2.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.github.pagehelper.PageHelper; @@ -13,6 +13,7 @@ import com.ycl.jxkg.context.WebContext; import com.ycl.jxkg.domain.entity.ExamPaper; import com.ycl.jxkg.domain.entity.Question; import com.ycl.jxkg.domain.entity.Subject; import com.ycl.jxkg.domain.exam.PaperFixQuestionDTO; import com.ycl.jxkg.domain.exam.PaperQuestion; import com.ycl.jxkg.domain.exam.PaperQuestionSettingDTO; @@ -22,16 +23,15 @@ import com.ycl.jxkg.domain.question.QuestionItemObject; import com.ycl.jxkg.domain.question.QuestionObject; import com.ycl.jxkg.domain.question.RandomQuestionDTO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperEditRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperPageRequestVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamPaperTitleItemVO; import com.ycl.jxkg.domain.vo.admin.exam.ExamResponseVO; import com.ycl.jxkg.domain.vo.admin.exam.*; import com.ycl.jxkg.domain.vo.student.dashboard.PaperFilter; import com.ycl.jxkg.domain.vo.student.dashboard.PaperInfo; import com.ycl.jxkg.domain.vo.student.exam.ExamPaperPageVO; import com.ycl.jxkg.enums.ExamPaperTypeEnum; import com.ycl.jxkg.enums.QuestionTypeEnum; import com.ycl.jxkg.enums.VisibilityEnum; import com.ycl.jxkg.enums.general.StatusEnum; import com.ycl.jxkg.excel.*; import com.ycl.jxkg.mapper.ExamPaperMapper; import com.ycl.jxkg.mapper.QuestionMapper; import com.ycl.jxkg.service.ExamPaperService; @@ -39,17 +39,20 @@ import com.ycl.jxkg.service.SubjectService; import com.ycl.jxkg.service.TextContentService; import com.ycl.jxkg.utils.DateTimeUtil; import com.ycl.jxkg.utils.JsonUtil; import com.ycl.jxkg.utils.PageInfoHelper; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.math.BigDecimal; import java.net.URLEncoder; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; @Service @@ -339,4 +342,229 @@ }); return list; } @Override @SneakyThrows public void importTemplate(HttpServletResponse response) { String fileName = URLEncoder.encode("试卷导入模板", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 构建模板样例数据 List<QuestionImportVO> data = new ArrayList<>(8); QuestionImportVO questionImportVO = new QuestionImportVO(); questionImportVO.setLabel("标题1"); questionImportVO.setQuestionType("单选题"); questionImportVO.setDifficult(2); questionImportVO.setCorrect("A"); questionImportVO.setScore(2); questionImportVO.setAnalyze("A是对的"); questionImportVO.setTitle("这是一道测试题目,使用该模板请删除或替换这道题"); questionImportVO.setOptionName("A"); questionImportVO.setOptionValue("选我"); data.add(questionImportVO); QuestionImportVO questionImport1 = new QuestionImportVO(); questionImport1.setOptionName("B"); questionImport1.setOptionValue("选B"); data.add(questionImport1); QuestionImportVO questionImport2 = new QuestionImportVO(); questionImport2.setOptionName("C"); questionImport2.setOptionValue("选C"); data.add(questionImport2); QuestionImportVO questionImport3 = new QuestionImportVO(); questionImport3.setOptionName("D"); questionImport3.setOptionValue("选D"); data.add(questionImport3); EasyExcel.write(response.getOutputStream(), QuestionImportVO.class) .sheet("模板") .registerWriteHandler(new FixedMergeCellStrategy(2, 4, Arrays.asList(2, 3, 6, 7, 8, 9))) .registerWriteHandler(new FixedMergeCellStrategy(2, 20, Arrays.asList(0, 1))) .doWrite(data); } @Override @SneakyThrows public void export(QuestionExportVO query, HttpServletResponse response) { // 查询导出数据 List<QuestionImportVO> exportData = questionService.export(query); // 构建数据 List<QuestionImportVO> exportList = new ArrayList<>(exportData.size() * 4); // 行合并规则 List<RowItem> 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.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<Subject> subjects = subjectService.list(); List<String> 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); } @Override @Transactional @SneakyThrows public Result importPaper(MultipartFile file, ExamPaperForm form) { List<Subject> subjects = subjectService.list(); List<Integer> questionNum = new ArrayList<>(); List<Integer> totalScore = new ArrayList<>(); // 拿到试卷 ExamPaper examPaper = ExamPaperForm.getEntityByForm(form, null); // 题目集合用于批量保存 ArrayList<Question> questions = new ArrayList<>(); // 一张试卷多个标题 ArrayList<PaperFixQuestionDTO> list = new ArrayList<>(); Consumer<List<QuestionImportVO>> consumer = (data) -> { // 循环每一行 for (int i = 0; i < data.size(); i++) { // 读取的题目 QuestionImportVO excelQuestion = data.get(i); String questionType = excelQuestion.getQuestionType(); String label = excelQuestion.getLabel(); // 判断是否标题 if (excelQuestion.master()) { // 一个标题多个题目 ArrayList<PaperQuestion> paperQuestions = new ArrayList<>(); // 循环题目 while (Boolean.TRUE) { // 更新读取的题目 excelQuestion = data.get(i); // 判断是否题目 if (excelQuestion.intact()) { Question question = new Question(); totalScore.add(excelQuestion.getScore()); // 该题的选项 List<QuestionItemObject> options = new ArrayList<>(8); // 选项内容 QuestionItemObject option = new QuestionItemObject(); option.setPrefix(excelQuestion.getOptionName()); option.setContent(excelQuestion.getOptionValue()); options.add(option); // 循环选项 while (Boolean.TRUE) { // 判断是否是最后一条 if (i + 1 == data.size()) { break; } QuestionImportVO nextQuestion = data.get(1 + i); if (nextQuestion.intact()) { break; } QuestionItemObject nextOption = new QuestionItemObject(); nextOption.setPrefix(nextQuestion.getOptionName()); nextOption.setContent(nextQuestion.getOptionValue()); options.add(nextOption); i++; } // 保存题目内容 QuestionObject questionObject = new QuestionObject(); questionObject.setQuestionItemObjects(options); questionObject.setAnalyze(excelQuestion.getAnalyze()); questionObject.setTitleContent(excelQuestion.getTitle()); questionObject.setCorrect(excelQuestion.getCorrect()); question.setTitle(excelQuestion.getTitle()); question.setContent(JSON.toJSONString(questionObject)); question.setQuestionType(QuestionTypeEnum.get(excelQuestion.getQuestionType())); // 答案(多选需要用、分割保存字符串到数据库) String[] corrects = excelQuestion.getCorrect().split("、"); if (corrects.length > 1) { question.setCorrect(String.join(",", corrects)); } else { question.setCorrect(excelQuestion.getCorrect()); } // 难度 question.setDifficult(excelQuestion.getDifficult()); // 创建人 question.setCreateUser(2); question.setStatus(StatusEnum.ENABLE); question.setCreateTime(new Date()); question.setDeleted(0); question.setQuestionType(QuestionTypeEnum.get(questionType)); // 根据科目名称获取id QuestionImportVO finalExcelQuestion = excelQuestion; question.setSubjectId(subjects.stream().filter(subject -> subject.getName().equals(finalExcelQuestion.getSubject())).findFirst().get().getId()); questions.add(question); PaperQuestion paperQuestion = new PaperQuestion(); BeanUtils.copyProperties(question, paperQuestion); paperQuestion.setItems(options); paperQuestion.setAnalyze(excelQuestion.getAnalyze()); paperQuestion.setScore(BigDecimal.valueOf(excelQuestion.getScore())); paperQuestions.add(paperQuestion); } if (i + 1 == data.size() || data.get(i + 1).master()) { break; } i++; } // 组装试卷内容 PaperFixQuestionDTO paperFixQuestionDTO = new PaperFixQuestionDTO(); paperFixQuestionDTO.setTitle(label); paperFixQuestionDTO.setQuestionType(QuestionTypeEnum.get(questionType)); paperFixQuestionDTO.setQuestionList(paperQuestions); questionNum.add(paperQuestions.size()); list.add(paperFixQuestionDTO); } } }; EasyExcel.read(file.getInputStream(), QuestionImportVO.class, new CurrencyDataListener(consumer)).sheet("模板").doRead(); // 保存题目 questionService.saveBatch(questions); // 保存试卷 examPaper.setContent(JSON.toJSONString(list)); examPaper.setVisibility(VisibilityEnum.fromCode(form.getVisibility()).getName()); examPaper.setCreateUser(2); examPaper.setCreateTime(new Date()); Integer score = totalScore.stream().reduce(Integer::sum).orElse(0); Integer num = questionNum.stream().reduce(Integer::sum).orElse(0); examPaper.setScore(new BigDecimal(score)); examPaper.setNum(num); examPaperMapper.insert(examPaper); return Result.ok(); } } src/main/java/com/ycl/jxkg/service/impl/QuestionServiceImpl.java
@@ -5,15 +5,17 @@ import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.ycl.jxkg.domain.entity.Question; import com.ycl.jxkg.enums.QuestionTypeEnum; import com.ycl.jxkg.enums.general.StatusEnum; import com.ycl.jxkg.domain.other.KeyValue; import com.ycl.jxkg.domain.question.QuestionItemObject; import com.ycl.jxkg.domain.question.QuestionObject; import com.ycl.jxkg.domain.vo.admin.exam.QuestionExportVO; import com.ycl.jxkg.domain.vo.admin.exam.QuestionImportVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionEditItemVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionEditRequestVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionPageRequestVO; import com.ycl.jxkg.domain.vo.admin.question.QuestionResponseVO; import com.ycl.jxkg.enums.QuestionTypeEnum; import com.ycl.jxkg.enums.general.StatusEnum; import com.ycl.jxkg.mapper.QuestionMapper; import com.ycl.jxkg.service.QuestionService; import com.ycl.jxkg.utils.DateTimeUtil; @@ -179,4 +181,9 @@ .update(); } @Override public List<QuestionImportVO> export(QuestionExportVO query) { return questionMapper.export(query); } } src/main/resources/mapper/QuestionMapper.xml
@@ -118,4 +118,43 @@ RAND() LIMIT #{num} </select> <resultMap id="exportDataMap" type="com.ycl.jxkg.domain.vo.admin.exam.QuestionExportData"> <result column="question_type" property="questionType"/> <result column="title" property="title"/> <result column="analyze" property="analyze"/> <result column="content" property="content"/> <result column="correct" property="answer"/> <collection property="subjectList" column="id" ofType="string" select="selectSubjects"/> </resultMap> <resultMap id="exportMap" type="com.ycl.jxkg.domain.vo.admin.exam.QuestionImportVO"> <result column="question_type" property="questionType"/> <result column="title" property="title"/> <result column="analyze" property="analyze"/> <result column="score" property="score"/> <result column="difficult" property="difficult"/> <result column="content" property="questionContent"/> <result column="score" property="score"/> <result column="correct" property="correct"/> <collection property="subjectList" column="id" ofType="string" select="selectSubjects"/> </resultMap> <select id="export" resultMap="exportMap"> SELECT DISTINCT q.*, ttc.content FROM t_question q INNER JOIN t_ex ttc on q.info_text_content_id = ttc.id AND q.deleted = 0 <where> <if test="query.examPaperId != null"> AND tqs.subject_id IN <foreach collection="query.subjectIds" open="(" separator="," close=")" item="subjectId"> #{subjectId} </foreach> </if> </where> </select> </mapper>