|
|
@ -1,6 +1,7 @@ |
|
|
|
package com.msdw.tms.service.impl; |
|
|
|
package com.msdw.tms.service.impl; |
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.date.DateUtil; |
|
|
|
import cn.hutool.core.date.DateUtil; |
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSON; |
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage; |
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage; |
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|
|
@ -61,6 +62,7 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
StringRedisTemplate stringRedisTemplate; |
|
|
|
StringRedisTemplate stringRedisTemplate; |
|
|
|
|
|
|
|
|
|
|
|
private String REMAINING_TINE_KEY = "REMAINING_TINE"; |
|
|
|
private String REMAINING_TINE_KEY = "REMAINING_TINE"; |
|
|
|
|
|
|
|
private String FAILURE_IMPORT_KEY = "FAILURE_IMPORT"; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 条件加分页查询,题干模糊查询,未删除,修改时间降序 |
|
|
|
* 条件加分页查询,题干模糊查询,未删除,修改时间降序 |
|
|
@ -319,10 +321,17 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
* 1、校验题干重复和题型不对的, |
|
|
|
* 1、校验题干重复和题型不对的, |
|
|
|
* 2、从列表中将该题剔除并记录index和导入失败的原因, |
|
|
|
* 2、从列表中将该题剔除并记录index和导入失败的原因, |
|
|
|
* 3、名称和选项内容都相同的要去重 |
|
|
|
* 3、名称和选项内容都相同的要去重 |
|
|
|
|
|
|
|
* 返回数据:token,成功几条,失败几条 |
|
|
|
|
|
|
|
* 将失败数据和失败信息保存在redis中,使用list类型,加上过期时间,key是token |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@Transactional |
|
|
|
@Transactional |
|
|
|
public List<String> importQuestion(MultipartFile file) throws IOException { |
|
|
|
public Map<String, String> importQuestion(MultipartFile file) throws IOException { |
|
|
|
|
|
|
|
//准备map接收导入失败的数据信息
|
|
|
|
|
|
|
|
Map<String, String> failureRecord = new HashMap<>(); |
|
|
|
|
|
|
|
//准备list收集失败数据
|
|
|
|
|
|
|
|
List<QuestionsImportFailureVO> failureVOs = new ArrayList<>(); |
|
|
|
|
|
|
|
|
|
|
|
// 参数合法性校验,只能上传.xlsx后缀的文件
|
|
|
|
// 参数合法性校验,只能上传.xlsx后缀的文件
|
|
|
|
if (StringUtils.isBlank(file.getOriginalFilename()) |
|
|
|
if (StringUtils.isBlank(file.getOriginalFilename()) |
|
|
|
|| !file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).equals(Constant.EXCEL_SUFFIX)) { |
|
|
|
|| !file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).equals(Constant.EXCEL_SUFFIX)) { |
|
|
@ -337,12 +346,28 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
ExceptionCast.cast(CommonCode.EXCEL_INVALID); |
|
|
|
ExceptionCast.cast(CommonCode.EXCEL_INVALID); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
List<String> failureRecord = new ArrayList<>(); |
|
|
|
//去掉重复数据,得到去重后的数据
|
|
|
|
|
|
|
|
ArrayList<QuestionsImportRequest> distinctList = list.stream().collect(Collectors.collectingAndThen(// 去重收集
|
|
|
|
|
|
|
|
Collectors.toCollection(() -> |
|
|
|
|
|
|
|
new TreeSet<>(Comparator.comparing(QuestionsImportRequest::toStringForCompare))), |
|
|
|
|
|
|
|
ArrayList::new |
|
|
|
|
|
|
|
)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//与元数据做差,得到重复的数据
|
|
|
|
|
|
|
|
List<QuestionsImportRequest> repeatFactor = removeRepeatFactor(distinctList, list); |
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < list.size(); i++) { |
|
|
|
//记录到失败信息列表
|
|
|
|
QuestionsImportRequest question = list.get(i); |
|
|
|
repeatFactor.forEach(question -> { |
|
|
|
|
|
|
|
QuestionsImportFailureVO failureVO = new QuestionsImportFailureVO(); |
|
|
|
|
|
|
|
BeanUtils.copyProperties(question, failureVO); |
|
|
|
|
|
|
|
failureVO.setFailureMsg(CommonCode.REPEAT_INEXCEL.message()); |
|
|
|
|
|
|
|
failureVOs.add(failureVO); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<QuestionsEntity> questions = distinctList.stream().map(question -> { |
|
|
|
//校验题干和内容重复
|
|
|
|
//校验题干和内容重复
|
|
|
|
QuestionsEntity questionsEntity = new QuestionsEntity(); |
|
|
|
QuestionsEntity questionsEntity = new QuestionsEntity(); |
|
|
|
|
|
|
|
//属性对拷
|
|
|
|
BeanUtils.copyProperties(question, questionsEntity); |
|
|
|
BeanUtils.copyProperties(question, questionsEntity); |
|
|
|
String questionTypeName = question.getQuestionTypeName(); |
|
|
|
String questionTypeName = question.getQuestionTypeName(); |
|
|
|
//根据题型名称得到题型号
|
|
|
|
//根据题型名称得到题型号
|
|
|
@ -357,8 +382,11 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
questionsEntity.setQuestionType(Constant.QuestionType.TRUE_OR_FALSE.getType()); |
|
|
|
questionsEntity.setQuestionType(Constant.QuestionType.TRUE_OR_FALSE.getType()); |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
// 题型不正确
|
|
|
|
// 题型不正确
|
|
|
|
failureRecord.add("第 " + question.getIndex() + " 行导入失败," + "题干:" + question.getQuestionStem() + ",失败信息:" + CommonCode.QUESTIONTYPE_INVALID.message()); |
|
|
|
QuestionsImportFailureVO failureVO = new QuestionsImportFailureVO(); |
|
|
|
continue; |
|
|
|
BeanUtils.copyProperties(question, failureVO); |
|
|
|
|
|
|
|
failureVO.setFailureMsg(CommonCode.QUESTIONTYPE_INVALID.message()); |
|
|
|
|
|
|
|
failureVOs.add(failureVO); |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//构造根据内容查询试题数量的Wrapper
|
|
|
|
//构造根据内容查询试题数量的Wrapper
|
|
|
@ -367,26 +395,63 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
int count = this.count(queryWrapper); |
|
|
|
int count = this.count(queryWrapper); |
|
|
|
|
|
|
|
|
|
|
|
if (count > 0) {//说明已存在
|
|
|
|
if (count > 0) {//说明已存在
|
|
|
|
failureRecord.add("第 " + question.getIndex() + " 行导入失败," + "题干:" + question.getQuestionStem() + ",失败信息:" + CommonCode.QUESTION_EXISTS.message()); |
|
|
|
QuestionsImportFailureVO failureVO = new QuestionsImportFailureVO(); |
|
|
|
continue; |
|
|
|
BeanUtils.copyProperties(question, failureVO); |
|
|
|
|
|
|
|
failureVO.setFailureMsg(CommonCode.QUESTION_EXISTS.message()); |
|
|
|
|
|
|
|
failureVOs.add(failureVO); |
|
|
|
|
|
|
|
return null; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
//TODO 创建者和修改者。。。
|
|
|
|
questionsEntity.setCreateTime(new Date()); |
|
|
|
questionsEntity.setCreateTime(new Date()); |
|
|
|
questionsEntity.setModifyTime(new Date()); |
|
|
|
questionsEntity.setModifyTime(new Date()); |
|
|
|
|
|
|
|
return questionsEntity; |
|
|
|
|
|
|
|
}).filter(Objects::nonNull)// 过滤掉为null的对象
|
|
|
|
|
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String token = ""; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 有导入失败的数据,才会存入redis
|
|
|
|
|
|
|
|
if (failureVOs.size() > 0) { |
|
|
|
|
|
|
|
//生成token
|
|
|
|
|
|
|
|
token = FAILURE_IMPORT_KEY + UUID.randomUUID().toString().replace("-", ""); |
|
|
|
|
|
|
|
|
|
|
|
this.save(questionsEntity); |
|
|
|
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); |
|
|
|
|
|
|
|
String failureVOJson = JSON.toJSONString(failureVOs); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ops.set(token, failureVOJson, 30 * 60, TimeUnit.SECONDS); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 批量保存
|
|
|
|
|
|
|
|
this.saveBatch(questions); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
failureRecord.put("token", token); |
|
|
|
|
|
|
|
failureRecord.put("successNum", (list.size() - failureVOs.size()) + ""); |
|
|
|
|
|
|
|
failureRecord.put("failureNum", failureVOs.size() + ""); |
|
|
|
|
|
|
|
|
|
|
|
return failureRecord; |
|
|
|
return failureRecord; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private <T> List<T> removeRepeatFactor(List<T> source, List<T> dest) { |
|
|
|
|
|
|
|
if (source != null && dest != null) { |
|
|
|
|
|
|
|
if (source.size() != 0 && dest.size() != 0) { |
|
|
|
|
|
|
|
Collection<T> A = new ArrayList<>(source); |
|
|
|
|
|
|
|
List<T> B = new ArrayList<>(dest); |
|
|
|
|
|
|
|
A.retainAll(B); |
|
|
|
|
|
|
|
if (A.size() != 0) { |
|
|
|
|
|
|
|
B.removeAll(A); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return B; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return dest; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 上传模板文件,修改模板信息表数据 |
|
|
|
* 上传模板文件,修改模板信息表数据 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
@Transactional |
|
|
|
@Transactional |
|
|
|
public FilesResult uploadFiles(MultipartFile file) throws IOException { |
|
|
|
public FilesResult uploadFiles(MultipartFile file) throws IOException { |
|
|
|
|
|
|
|
|
|
|
|
// 参数合法性校验,只能上传.xlsx后缀的文件
|
|
|
|
// 参数合法性校验,只能上传.xlsx后缀的文件
|
|
|
|
if (StringUtils.isBlank(file.getOriginalFilename()) |
|
|
|
if (StringUtils.isBlank(file.getOriginalFilename()) |
|
|
|
|| !file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).equals(Constant.EXCEL_SUFFIX)) { |
|
|
|
|| !file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")).equals(Constant.EXCEL_SUFFIX)) { |
|
|
@ -493,12 +558,10 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
//3、通过工具类下载文件
|
|
|
|
//3、通过工具类下载文件
|
|
|
|
new ExcelExportUtil(QuestionsImportRequest.class, Constant.ROW_INDEX, Constant.STYLE_INDEX). |
|
|
|
new ExcelExportUtil(QuestionsImportRequest.class, Constant.ROW_INDEX, Constant.STYLE_INDEX). |
|
|
|
export(response, fis, list, "试题表.xlsx"); |
|
|
|
export(response, fis, list, "试题表.xlsx"); |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public String getEvaluationRemainingTime(Integer userId) { |
|
|
|
public String getEvaluationRemainingTime(Integer userId) { |
|
|
|
|
|
|
|
|
|
|
|
//时长转成秒来计算
|
|
|
|
//时长转成秒来计算
|
|
|
|
//先查询该用户有没有开始考试,既redis中有没有跟该用户id相关联的key
|
|
|
|
//先查询该用户有没有开始考试,既redis中有没有跟该用户id相关联的key
|
|
|
|
//如果没有,表示是开始测评,向redis中插入一条数据,key是前缀加用户id,value是当前时间,过期时间是测评时长加三十秒
|
|
|
|
//如果没有,表示是开始测评,向redis中插入一条数据,key是前缀加用户id,value是当前时间,过期时间是测评时长加三十秒
|
|
|
@ -526,6 +589,37 @@ public class QuestionsServiceImpl extends ServiceImpl<QuestionsDao, QuestionsEnt |
|
|
|
return DateUtil.secondToTime(remainingTime); |
|
|
|
return DateUtil.secondToTime(remainingTime); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void exportFailureRecord(HttpServletResponse response, String token) throws Exception { |
|
|
|
|
|
|
|
if (StringUtils.isEmpty(token)) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue(); |
|
|
|
|
|
|
|
//获取数据
|
|
|
|
|
|
|
|
String record = ops.get(token); |
|
|
|
|
|
|
|
if (StringUtils.isEmpty(record)) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<QuestionsImportFailureVO> parse = (List<QuestionsImportFailureVO>) JSON.parse(record); |
|
|
|
|
|
|
|
parse.sort(Comparator.comparing(QuestionsImportFailureVO::getIndex)); |
|
|
|
|
|
|
|
// Collections.sort(parse, new Comparator<QuestionsImportFailureVO>() {
|
|
|
|
|
|
|
|
// @Override
|
|
|
|
|
|
|
|
// public int compare(QuestionsImportFailureVO o1, QuestionsImportFailureVO o2) {
|
|
|
|
|
|
|
|
// //升序
|
|
|
|
|
|
|
|
// return o1.getIndex().compareTo(o2.getIndex());
|
|
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
// });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//2.加载模板流数据
|
|
|
|
|
|
|
|
org.springframework.core.io.Resource resource = new ClassPathResource("excel-template/试题导入失败数据导出模板.xlsx"); |
|
|
|
|
|
|
|
FileInputStream fis = new FileInputStream(resource.getFile()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//3、通过工具类下载文件
|
|
|
|
|
|
|
|
new ExcelExportUtil(QuestionsImportRequest.class, Constant.ROW_INDEX, Constant.STYLE_INDEX). |
|
|
|
|
|
|
|
export(response, fis, parse, "导入失败数据表.xlsx"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Set<QuestionsVO> getRandomList(int len, List<QuestionsEntity> list) { |
|
|
|
private Set<QuestionsVO> getRandomList(int len, List<QuestionsEntity> list) { |
|
|
|
Set<QuestionsVO> set = new HashSet<>(); |
|
|
|
Set<QuestionsVO> set = new HashSet<>(); |
|
|
|
Random random = new Random(); |
|
|
|
Random random = new Random(); |
|
|
|