fuliqi
2024-07-05 3179257af77acbc229fe873de7495e2d3f8dfb6e
Merge remote-tracking branch 'origin/dev' into dev
8个文件已修改
9个文件已添加
840 ■■■■■ 已修改文件
src/main/java/com/ycl/jxkg/controller/admin/ExamPaperController.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/domain/vo/admin/exam/OptionAndValueVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/domain/vo/admin/exam/QuestionExportData.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/domain/vo/admin/exam/QuestionExportVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/domain/vo/admin/exam/QuestionImportVO.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/enums/QuestionTypeEnum.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/excel/CurrencyDataListener.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/excel/DynamicMergeCellStrategy.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/excel/FixedMergeCellStrategy.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/excel/RowItem.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/excel/SelectExcel.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/mapper/QuestionMapper.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/service/ExamPaperService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/service/QuestionService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/service/impl/ExamPaperServiceImpl.java 242 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ycl/jxkg/service/impl/QuestionServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/QuestionMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>