一、业务背景介绍:最近在开发一个考试功能模块,在试题录入部分需要做批量导入试题的功能,试题类型有:单选题、多选题、判断题、简答题已经应用题,现在做的是一张表里可以按要求填入多种类型试题,也就是一次可以导入多种类型试题。导入模板截图如下:
二、实现思路:由于后台试题关联的表有两张表,因此模板和后台试题实体类并不是一一对应的关系,因此需要在后台定义一个类型于试题实体类但和模板字段对应的试题类,之后再在后台解析这种模板实体类集合,拼接成现有试题增加方法里需要的格式,进行一一添加。
package com.alvis.exam.viewmodel.admin.question;
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
@Data
@ToString
@EqualsAndHashCode
@NoArgsConstructor
@Accessors(chain = true)
public class QuestionTemplateVM implements Serializable{
private static final long serialVersionUID = 1L;
//试题id
private Integer id;
/**题目*/
@NotBlank
@Excel(name = "试题名称", height= 40)
private String title;
/**题目类型*/
@NotBlank
@Excel(name = "试题类型", prompt="请填对应的数字 1.单选题 2.多选题 3.判断题 5.简答题 6.应用题", height= 40)
//1.单选题 2.多选题 3.判断题 5简答题 6.应用题
private Integer questionType;
/**试题等级*/
@NotBlank
@Excel(name = "试题等级", prompt="请填对应的数字 1.一级 2.二级 3.三级 4.四级 5.五级 6.六级 7.七级 8.八级", height= 40)
private Integer questionLevel;
/**考核类别*/
@NotBlank
@Excel(name = "考核类别", height= 40, prompt="请填对应的数字 4.一级测评 5.二级测评 6.三级测评 7.四级测评 8.五级测评 9.六级测评 10.七级测评 11.八级测评")
private Integer testId;
/**选项A*/
@Excel(name = "选项A", height= 40)
private String optionA;
/**选项B*/
@Excel(name = "选项B", height= 40)
private String optionB;
/**选项C*/
@Excel(name = "选项C", height= 40)
private String optionC;
/**选项D*/
@Excel(name = "选项D", height= 40)
private String optionD;
/**选项E*/
@Excel(name = "选项E", height= 40)
private String optionE;
/**选项F*/
@Excel(name = "选项F", height= 40)
private String optionF;
/**正确答案*/
@Excel(name = "单/判/填/应/简的答案", width=24, height= 40, prompt="1.单选/多选/判断/简答/应用题的答案 2.为ABCD其中的一个字母 3.或者是一段文字")
//单选题/判断题/填空题/应用题答案, 例如:A
private String correct;
/**正确答案*/
@Excel(name = "多选题答案", width=20, height= 40, prompt="1.多选题答案 2.至少两个选项以上 3.且以英文逗号隔开 4.例如:A,B")
//多选题答案,例如:A,B 以英文逗号隔开
private String correctArray;
/**解析*/
@NotBlank
@Excel(name = "答案解析", height= 40)
private String analyze;
/**分数*/
@NotBlank
@Excel(name = "分数", height= 40)
private String score;
/**难度星数*/
@NotBlank
@Excel(name = "难度星数", height= 40, prompt="1.代表难度星星的数量 2.填数字 3.最大5,最小1")
//难度星数:1到5的数字
private Integer difficult;
}
三、关键代码展示(QuestionController 和 QuestionServiceImpl)
A.QuestionController 方法代码展示如下:
/**
* PC端后台系统试题批量导入功能
* @param file
* @param updateSupport
* @return
* @throws Exception
*/
@Log(title = "试题导入管理", businessType = BusinessType.IMPORT)
@PreAuthorize("@ss.hasPermi('exam:question:import')")
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception{
//解析Excel表格,拿到导入实体集合List<QuestionTemplateVM>
ExcelUtil<QuestionTemplateVM> util = new ExcelUtil<QuestionTemplateVM>(QuestionTemplateVM.class);
List<QuestionTemplateVM> abKhstList = util.importExcel(file.getInputStream());
//这是一个带选项前缀、选项对应内容以及选项对应分数的实体类
List<QItemVO> items = null;
//QRequestVO此格式就是后台试题增加方法里需要的格式
List<QRequestVO> QRequestVOList = new ArrayList<>();
for(QuestionTemplateVM questionTemplateVM : abKhstList) {
//导入试题标题和试题类型不能为空,要不然不进方法
if(questionTemplateVM.getTitle() != "" && questionTemplateVM.getQuestionType() != null) {
items = new ArrayList<>();
QRequestVO qRequestVO = new QRequestVO();
QItemVO qItemVO = null;
//说明是单选题
if(questionTemplateVM.getQuestionType() == 1) {
for (int i = 0; i < 4; i++) {
switch (String.valueOf(i)) {
case "0":
qItemVO = new QItemVO();
qItemVO.setPrefix("A");
qItemVO.setContent(questionTemplateVM.getOptionA());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "1":
qItemVO = new QItemVO();
qItemVO.setPrefix("B");
qItemVO.setContent(questionTemplateVM.getOptionB());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "2":
qItemVO = new QItemVO();
qItemVO.setPrefix("C");
qItemVO.setContent(questionTemplateVM.getOptionC());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "3":
qItemVO = new QItemVO();
qItemVO.setPrefix("D");
qItemVO.setContent(questionTemplateVM.getOptionD());
qItemVO.setScore(null);
items.add(qItemVO);
break;
default:
break;
}
}
}else if(questionTemplateVM.getQuestionType() == 2) {//说明多选题
if(questionTemplateVM.getOptionF() != "") {//说明有6个选项
for (int i = 0; i < 6; i++) {
switch (String.valueOf(i)) {
case "0":
qItemVO = new QItemVO();
qItemVO.setPrefix("A");
qItemVO.setContent(questionTemplateVM.getOptionA());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "1":
qItemVO = new QItemVO();
qItemVO.setPrefix("B");
qItemVO.setContent(questionTemplateVM.getOptionB());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "2":
qItemVO = new QItemVO();
qItemVO.setPrefix("C");
qItemVO.setContent(questionTemplateVM.getOptionC());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "3":
qItemVO = new QItemVO();
qItemVO.setPrefix("D");
qItemVO.setContent(questionTemplateVM.getOptionD());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "4":
qItemVO = new QItemVO();
qItemVO.setPrefix("E");
qItemVO.setContent(questionTemplateVM.getOptionE());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "5":
qItemVO = new QItemVO();
qItemVO.setPrefix("F");
qItemVO.setContent(questionTemplateVM.getOptionF());
qItemVO.setScore(null);
items.add(qItemVO);
break;
default:
break;
}
}
}else if(questionTemplateVM.getOptionF() == "" && questionTemplateVM.getOptionE() != "") {//说明有5个选项
for (int i = 0; i < 6; i++) {
switch (String.valueOf(i)) {
case "0":
qItemVO = new QItemVO();
qItemVO.setPrefix("A");
qItemVO.setContent(questionTemplateVM.getOptionA());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "1":
qItemVO = new QItemVO();
qItemVO.setPrefix("B");
qItemVO.setContent(questionTemplateVM.getOptionB());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "2":
qItemVO = new QItemVO();
qItemVO.setPrefix("C");
qItemVO.setContent(questionTemplateVM.getOptionC());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "3":
qItemVO = new QItemVO();
qItemVO.setPrefix("D");
qItemVO.setContent(questionTemplateVM.getOptionD());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "4":
qItemVO = new QItemVO();
qItemVO.setPrefix("E");
qItemVO.setContent(questionTemplateVM.getOptionE());
qItemVO.setScore(null);
items.add(qItemVO);
break;
default:
break;
}
}
}else {//必须有四个选项
for (int i = 0; i < 4; i++) {
switch (String.valueOf(i)) {
case "0":
qItemVO = new QItemVO();
qItemVO.setPrefix("A");
qItemVO.setContent(questionTemplateVM.getOptionA());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "1":
qItemVO = new QItemVO();
qItemVO.setPrefix("B");
qItemVO.setContent(questionTemplateVM.getOptionB());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "2":
qItemVO = new QItemVO();
qItemVO.setPrefix("C");
qItemVO.setContent(questionTemplateVM.getOptionC());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "3":
qItemVO = new QItemVO();
qItemVO.setPrefix("D");
qItemVO.setContent(questionTemplateVM.getOptionD());
qItemVO.setScore(null);
items.add(qItemVO);
break;
default:
break;
}
}
}
//试题模板中多选题正确答案是以逗号隔开字符串格式,后台需要转成数组格式进行保存
if(questionTemplateVM.getCorrectArray() != "") {
String cArrayString = questionTemplateVM.getCorrectArray();
String[] arrayValue = cArrayString.split(",");
List<String> strs = Arrays.asList(arrayValue);
qRequestVO.setCorrectArray(strs);
}
}else if(questionTemplateVM.getQuestionType() == 3) {//说明判断题
for (int i = 0; i < 2; i++) {
switch (String.valueOf(i)) {
case "0":
qItemVO = new QItemVO();
qItemVO.setPrefix("A");
qItemVO.setContent(questionTemplateVM.getOptionA());
qItemVO.setScore(null);
items.add(qItemVO);
break;
case "1":
qItemVO = new QItemVO();
qItemVO.setPrefix("B");
qItemVO.setContent(questionTemplateVM.getOptionB());
qItemVO.setScore(null);
items.add(qItemVO);
break;
default:
break;
}
}
}else if(questionTemplateVM.getQuestionType() == 5 || questionTemplateVM.getQuestionType() == 6) {//说明简答题和应用题
System.out.println("简答题和应用题进入!");
}
qRequestVO.setItems(items);
//复制模板questionTemplateVM相同的属性值到qRequestVO里
BeanUtils.copyProperties(questionTemplateVM, qRequestVO);
QRequestVOList.add(qRequestVO);
//System.out.println(qRequestVO);
}
}
//获取当前操作用户信息
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
//获取当前用户名称
String operName = loginUser.getUsername();
//调取相关方法批量添加试题,并进行试题格式验证和错误信息记录,并返回给前台进行提示
String message = questionService.importAbKhSt(QRequestVOList, updateSupport, operName);
return AjaxResult.success(message);
}
B.QuestionServiceImpl 方法代码展示如下:
/**
* 导入主观题数据
*
* @param abKhZgstList 试题数据列表
* @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据
* @param operName 操作用户
* @return 结果
*/
@Override
public String importAbKhSt(List<QRequestVO> qRequestVOList, Boolean isUpdateSupport, String operName){
int successNum = 0;
int failureNum = 0;
int incorFormat = 0;
StringBuilder successMsg = new StringBuilder();
StringBuilder failureMsg = new StringBuilder();
StringBuilder incorFormatMsg = new StringBuilder();
if (StringUtils.isNull(qRequestVOList) || qRequestVOList.size() == 0){
//throw new CustomException("导入主观题数据不能为空!");
successMsg.insert(0, "导入试题数据不能为空!");
}
for (QRequestVO qRequestVO : qRequestVOList){
try{
// 验证是否存在这个主观题
Question u = questionMapper.selectQByTitle(qRequestVO.getTitle());
if (StringUtils.isNull(u)){
if(qRequestVO.getQuestionType() == 1 || qRequestVO.getQuestionType() == 3) {
if(qRequestVO.getTestId() == null || qRequestVO.getQuestionLevel() == null) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>试题等级/考核类别不能为空</span>");
}else if(qRequestVO.getCorrect().equals("") || qRequestVO.getAnalyze().equals("")){
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>答案/答案解析不能为空</span>");
}else if(qRequestVO.getScore() == null || qRequestVO.getDifficult() == null) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>分数/难度星数不能为空</span>");
}else {
//调取验证选项内容不为空和不重复的方法
QJudgeObject qObject1 = this.judgeAnswerOrOption(qRequestVO, successNum, incorFormat, successMsg, incorFormatMsg);
successNum = qObject1.getSuccessNum();
incorFormat = qObject1.getIncorFormat();
incorFormatMsg = qObject1.getIncorFormatMsg();
successMsg = qObject1.getSuccessMsg();
}
}else if(qRequestVO.getQuestionType() == 5 || qRequestVO.getQuestionType() == 6 ){
if(qRequestVO.getTestId() == null || qRequestVO.getQuestionLevel() == null) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>试题等级/考核类别不能为空</span>");
}else if(qRequestVO.getCorrect().equals("") || qRequestVO.getAnalyze().equals("")){
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>答案/答案解析不能为空</span>");
}else if(qRequestVO.getScore() == null || qRequestVO.getDifficult() == null) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>分数/难度星数不能为空</span>");
}else {
this.insertFullQuestion(qRequestVO, (SecurityUtils.getLoginUser().getUser().getUserId()).intValue());
//this.insertAbKhZgst(abKhZgst);
successNum++;
successMsg.append("<br/>" + successNum + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:blue'>导入成功</span>");
}
}else if(qRequestVO.getQuestionType() == 2){
if(qRequestVO.getTestId() == null || qRequestVO.getQuestionLevel() == null) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>试题等级/考核类别不能为空</span>");
}else if(StringUtils.isNull(qRequestVO.getCorrectArray()) || qRequestVO.getAnalyze().equals("")){
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>答案/答案解析不能为空</span>");
}else if(qRequestVO.getScore() == null || qRequestVO.getDifficult() == null) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>分数/难度星数不能为空</span>");
}else {
//调取验证选项内容不为空和不重复的方法
QJudgeObject qObject2 = this.judgeAnswerOrOption(qRequestVO, successNum, incorFormat, successMsg, incorFormatMsg);
successNum = qObject2.getSuccessNum();
incorFormat = qObject2.getIncorFormat();
incorFormatMsg = qObject2.getIncorFormatMsg();
successMsg = qObject2.getSuccessMsg();
}
}
}else if (isUpdateSupport){
this.updateFullQuestionImport(qRequestVO, u.getId());
successNum++;
successMsg.append("<br/>" + successNum + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:green'>更新成功</span>");
}else{
failureNum++;
failureMsg.append("<br/>" + failureNum + "、试题名称为:" + qRequestVO.getTitle() + " <span style='color:red'>已存在</span>");
}
}catch (Exception e){
System.out.println("bug:"+e);
failureNum++;
String msg = "<br/>" + failureNum + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>导入失败</span>:";
failureMsg.append(msg + e.getMessage());
}
}
if (failureNum > 0 && successNum == 0 && incorFormat == 0){
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
return failureMsg.toString();
}else if(successNum > 0 && failureNum == 0 && incorFormat == 0){
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
return successMsg.toString();
}else if(incorFormat > 0 && failureNum == 0 && successNum == 0){
return incorFormatMsg.toString();
}else if(failureNum == 0 && successNum == 0){
return successMsg.toString();
}else {
int totalFailNum = failureNum + incorFormat;
successMsg.insert(0, "请注意,共 " + successNum + " 条导入成功,共 " + totalFailNum + " 条导入失败!,详情如下:");
return successMsg.toString() + failureMsg.toString() + incorFormatMsg.toString();
}
}
/**
* 判断选项是否为空已经判断选项是否重复,并返回相关统计数和信息
* @param qRequestVO
* @param successNum
* @param incorFormat
* @param successMsg
* @param incorFormatMsg
* @return
*/
public QJudgeObject judgeAnswerOrOption(QRequestVO qRequestVO, Integer successNum, Integer incorFormat, StringBuilder successMsg, StringBuilder incorFormatMsg){
QJudgeObject qJudgeObject = new QJudgeObject();
//存放的值是无序和唯一的,如何往里添加重复的值,会进行合并
HashSet<String> hashSet = new HashSet<String>();
int setNum = 0;
String pingStr = null;
boolean isNullFlag = false;
for(QItemVO qItemVO : qRequestVO.getItems()) {
if (StringUtils.isEmpty(qItemVO.getContent())) {
isNullFlag = true;
if (pingStr == null) {
pingStr = qItemVO.getPrefix();
}else {
pingStr = pingStr + "/" + qItemVO.getPrefix();
}
}else {
setNum ++;
hashSet.add(qItemVO.getContent());
}
}
if (isNullFlag) {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>" + pingStr + "选项内容不能为空</span>");
}else {
//说明各选项中没有重复值
if (hashSet.size() == setNum && setNum != 0) {
this.insertFullQuestion(qRequestVO, (SecurityUtils.getLoginUser().getUser().getUserId()).intValue());
//this.insertAbKhZgst(abKhZgst);
successNum++;
successMsg.append("<br/>" + successNum + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:blue'>导入成功</span>");
}else {
incorFormat++;
incorFormatMsg.append("<br/>" + incorFormat + "、试题名称为: " + qRequestVO.getTitle() + " <span style='color:red'>选项内容有重复值</span>");
}
}
qJudgeObject.setIncorFormat(incorFormat);
qJudgeObject.setIncorFormatMsg(incorFormatMsg);
qJudgeObject.setSuccessMsg(successMsg);
qJudgeObject.setSuccessNum(successNum);
return qJudgeObject;
}
QuestionServiceImpl里用的一个返回实体类如下:
import lombok.Data;
/**
* QuestionServiceImpl里用的一个返回实体类
* @author hc
*
*/
@Data
public class QJudgeObject {
//成功数
private int successNum;
//失败数
private int failureNum;
//格式不正确数
private int incorFormat;
//成功信息
private StringBuilder successMsg;
//失败信息
private StringBuilder failureMsg;
//不合适信息
private StringBuilder incorFormatMsg;
}
四、试题批量导入提示效果图如下: