@ -43,15 +43,20 @@ import javax.validation.constraints.NotNull;
import java.math.BigDecimal ;
import java.util.* ;
import java.util.function.Function ;
import java.util.function.Predicate ;
import java.util.stream.Collectors ;
import static cn.hutool.core.date.DateUnit.SECOND ;
import static cn.hutool.core.util.ArrayUtil.isAllNotEmpty ;
import static cn.hutool.core.util.ArrayUtil.join ;
import static cn.hutool.core.util.ObjectUtil.defaultIfNull ;
import static cn.jlw.util.CacheUserUtil.getStudent ;
import static com.ibeetl.admin.core.util.user.CacheUserUtil.getUserId ;
import static com.ibeetl.jlw.enums.QuestionBusinessTypeEnum.FROM_OPEN_COURSE ;
import static com.ibeetl.jlw.enums.QuestionLogAddTypeEnum.* ;
import static com.ibeetl.jlw.enums.ResourcesQuestionSnapshotFromTypeEnum.CHAPTER_EXERCISE ;
import static com.ibeetl.jlw.enums.ResourcesQuestionTypeEnum.isOneQuestion ;
import static java.lang.Math.abs ;
import static java.math.BigDecimal.ZERO ;
import static java.util.stream.Collectors.groupingBy ;
import static java.util.stream.Collectors.joining ;
@ -72,6 +77,7 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
@Autowired @Lazy
private TeacherOpenCourseQuestionSettingService teacherOpenCourseQuestionSettingService ;
@Autowired private TeacherOpenCourseQuestionLogWrongService teacherOpenCourseQuestionLogWrongService ;
@Autowired private QuestionLogSummaryService questionLogSummaryService ;
public PageQuery < TeacherOpenCourseQuestionLog > queryByCondition ( PageQuery query ) {
PageQuery ret = teacherOpenCourseQuestionLogDao . queryByCondition ( query ) ;
queryListAfter ( ret . getList ( ) ) ;
@ -297,20 +303,17 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
final Long questionSettingId = questionLogAddDTO . getQuestionSettingId ( ) ;
Map < String , String > questionLogMap = questionLogAddDTO . getQuestionLogMap ( ) ;
// 练习提交验证
validateTestAddTypeThrow ( questionLogAddDTO . getQuestionLogAddType ( ) ) ;
// 查询学生身份
final Student student = getStudent ( ) ;
Assert . notNull ( student , "非学生身份,无法提交!" ) ;
TeacherOpenCourseQuestionSetting info = teacherOpenCourseQuestionSettingService . getInfo ( questionSettingId ) ;
Assert . notNull ( info, "题目配置ID有误 ! ") ;
TeacherOpenCourseQuestionSetting questionSetting = teacherOpenCourseQuestionSettingService . getInfo ( questionSettingId ) ;
Assert . notNull ( questionSetting, "未匹配到试卷信息 ! ") ;
// 校验当前时间是否在考试的时间段
teacherOpenCourseQuestionSettingService . verifyQuestionStartAndEndTimeWithNowTimeThrow ( questionSettingId ) ;
switch ( info . getTeacherOpenCourseQuestionSettingType ( ) ) {
switch ( questionSetting . getTeacherOpenCourseQuestionSettingType ( ) ) {
case HOMEWORK_FILE : {
addFileRelatedLog ( join ( questionLogMap . values ( ) . toArray ( ) , "," ) , questionSettingId , student ) ;
} break ;
@ -319,9 +322,7 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
case CHAPTER_EXERCISE :
case EXAM :
case HOMEWORK_QUESTION : {
addQuestionRelatedLog ( questionLogAddDTO , student , item - > {
return item . getQuestionLogAddType ( ) = = null | | PRE_SUBMIT . equals ( item . getQuestionLogAddType ( ) ) ;
} ) ;
addQuestionRelatedLog ( questionLogAddDTO , questionSetting , student ) ;
} break ;
}
}
@ -341,21 +342,16 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
final Student student = getStudent ( ) ;
Assert . notNull ( student , "非学生身份,无法提交!" ) ;
TeacherOpenCourseQuestionSetting info = teacherOpenCourseQuestionSettingService . getInfo ( questionSettingId ) ;
Assert . notNull ( info, "题目配置ID有误 ! ") ;
TeacherOpenCourseQuestionSetting questionSetting = teacherOpenCourseQuestionSettingService . getInfo ( questionSettingId ) ;
Assert . notNull ( questionSetting, "未匹配到试卷信息 ! ") ;
// 校验当前时间是否在考试的时间段
teacherOpenCourseQuestionSettingService . verifyQuestionStartAndEndTimeWithNowTimeThrow ( questionSettingId ) ;
switch ( info . getTeacherOpenCourseQuestionSettingType ( ) ) {
switch ( questionSetting . getTeacherOpenCourseQuestionSettingType ( ) ) {
// 这些都和题目相关,暂时先放在一个方法
case CHAPTER_EXERCISE : {
// 练习可以提交到几个类型
final List < QuestionLogAddTypeEnum > questionLogAddTypeEnums =
Arrays . asList ( ERROR_TEST_PRE_SUBMIT , ERROR_TEST_FINALLY_SUBMIT , TUCK_TEST_PRE_SUBMIT , TUCK_TEST_FINALLY_SUBMIT ) ;
addQuestionRelatedLog ( questionLogAddDTO , student , item - > {
return questionLogAddTypeEnums . contains ( item . getQuestionLogAddType ( ) ) ;
} ) ;
addQuestionRelatedLog ( questionLogAddDTO , questionSetting , student ) ;
} break ;
}
}
@ -389,14 +385,15 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
* @param questionLogAddDTO 题 目 信 息
* @param student 学 生 信 息
* /
private void addQuestionRelatedLog ( @NotNull ( message = "提交题目信息不能为空" ) QuestionLogAddDTO questionLogAddDTO , final Student student , Predicate < TeacherOpenCourseQuestionLog > predicate ) {
private void addQuestionRelatedLog ( @NotNull ( message = "提交题目信息不能为空" ) QuestionLogAddDTO questionLogAddDTO ,
@NotNull ( message = "开课信息不能为空" ) TeacherOpenCourseQuestionSetting questionSetting ,
@NotNull ( message = "学生信息不能为空" ) final Student student ) {
// 参数获取
QuestionLogAddTypeEnum addType = questionLogAddDTO . getQuestionLogAddType ( ) ;
Date addTime = questionLogAddDTO . getAddTime ( ) ; Long studentId = student . getStudentId ( ) ;
Long questionSettingId = questionLogAddDTO . getQuestionSettingId ( ) ;
Map < String , String > questionLogMap = questionLogAddDTO . getQuestionLogMap ( ) ;
// 查询符合条件的日志表
String questionSnapshotIds = join ( questionLogMap . keySet ( ) . toArray ( ) , "," ) ;
@ -410,9 +407,6 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
. build ( ) ;
List < TeacherOpenCourseQuestionLog > logList = teacherOpenCourseQuestionLogDao . getValuesByQuery ( questionLogQuery ) ;
// 排除条件交给外部调用
logList = logList . stream ( ) . filter ( predicate ) . collect ( Collectors . toList ( ) ) ;
Assert . notEmpty ( logList , "未查询到题目信息!" ) ;
// 当前时间, 存储要更新的题目日志集合
@ -427,16 +421,11 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
validateQuestionLogAddTypeThrow ( questionLog . getQuestionLogAddType ( ) , questionLogAddDTO . getQuestionLogAddType ( ) ) ;
// 学生提交的答案
String answersText = join ( questionLogMap . get ( questionLog . getResourcesQuestionSnapshotId ( ) . toString ( ) ) . split ( "," ) , "," ) ;
// 是否是正确答案
Boolean isCorrectAnswer = questionLog . getQuestionAnswer ( ) . equalsIgnoreCase ( answersText ) ;
// 完成时间,从生成试卷时候,开始计算
long finishSecondTime = DateUtil . between ( questionLog . getTeacherOpenCourseQuestionLogAddTime ( ) , now , SECOND ) ;
// 填充属性
questionLog . setTeacherOpenCourseQuestionLogAnswer ( answersText ) ;
// 计算该题目学生的得分情况
questionLog . setStudentScore ( isCorrectAnswer ? questionLog . getQuestionScore ( ) : BigDecimal . valueOf ( 0 ) ) ;
questionLog . setIsErrorFavorite ( isCorrectAnswer ) ;
questionLog . setTeacherOpenCourseQuestionLogUpdateTime ( now ) ;
questionLog . setTeacherOpenCourseQuestionLogFinishTime ( finishSecondTime ) ;
questionLog . setQuestionLogAddType ( addType ) ;
@ -446,17 +435,168 @@ public class TeacherOpenCourseQuestionLogService extends CoreBaseService<Teacher
// 只添加可以更新的数据
updateList . add ( questionLog ) ;
} ) ;
// 计算学生分数,并标记错题
calculateScoreOnSubmit ( updateList ) ;
// 学生做的题目的答案与日志关联
updateBatchTemplate ( updateList ) ;
// 最后提交试卷答案时候,才会记录错题
if ( addType . equals ( FINALLY_SUBMIT ) ) {
// 添加到题目日志汇总中
addQuestionLogSummary ( updateList , logList . size ( ) , student ,
questionSetting . getTeacherOpenCourseQuestionSettingName ( ) , questionSetting . getTeacherOpenCourseQuestionSettingType ( ) ) ;
// 批量插入错题集( 错题库) , 方法内部自带分数判断。内部方法更新log中的错题标记。
teacherOpenCourseQuestionLogWrongService . insertBatchByQuestionLogList ( logList ) ;
teacherOpenCourseQuestionLogWrongService . insertBatchByQuestionLogList ( update List) ;
}
}
/ * *
* 功 能 描 述 : < br >
* 计 算 学 生 分 数
* 会 判 断 题 目 是 否 打 完 , 并 抛 出 异 常
*
* @param logList
* @Author : lx
* @Date : 2022 / 12 / 4 20 : 16
* /
public void calculateScoreOnSubmit ( @NotEmpty ( message = "学生题目日志不能为空!" ) List < TeacherOpenCourseQuestionLog > logList ) {
logList . forEach ( questionLog - > {
// 题目原本设置的分数
final BigDecimal questionScore = questionLog . getQuestionScore ( ) ;
// 学生提交的结果
final String studentAnswer = defaultIfNull ( questionLog . getTeacherOpenCourseQuestionLogAnswer ( ) , "" ) ;
// 题目的答案
final String questionAnswer = defaultIfNull ( questionLog . getQuestionAnswer ( ) , "" ) ;
// 判断答案和学生提交的结果,都不能为空
final boolean allNotEmpty = isAllNotEmpty ( questionAnswer , studentAnswer ) ;
// 题目类型,答案属于一道题
final boolean oneQuestion = isOneQuestion ( questionLog . getQuestionType ( ) ) ;
// 一条日志记录,属于一道题
if ( oneQuestion ) {
boolean isTrue = allNotEmpty & & studentAnswer . equalsIgnoreCase ( questionAnswer ) ;
// 正确题目的数量
Integer isTrueInteger = BooleanUtil . toInteger ( isTrue ) ;
final BigDecimal mySimpleScore = isTrue ? questionScore : ZERO ;
// 计算学生最后的得分
questionLog . setStudentScore ( mySimpleScore ) ;
questionLog . setSuccessCount ( isTrueInteger ) ;
questionLog . setErrorCount ( 1 - isTrueInteger ) ;
// 错题标记
questionLog . setIsErrorFavorite ( ! isTrue ) ;
}
// 一条日志记录,属于多道题的逻辑处理
else {
// 分数,正确数量
BigDecimal score = ZERO ; int successCount = 0 ;
// 学生提交的题目结果
final String [ ] studentAnswerArr = studentAnswer . split ( "," ) ;
// 正确答案和学生答案对比
String [ ] successAnswerArr = questionAnswer . split ( "," ) ;
final int studentAnswerLength = studentAnswerArr . length ;
final int questionLength = successAnswerArr . length ;
// 断言需要判断,题目答案的数量是否相等,不然会对不上答案,导致分数计算错误
Assert . isTrue ( studentAnswerLength = = questionLength , "题干:" + questionLog . getQuestionStem ( ) + ",有选项未提交!" ) ;
// 一道题的分数
BigDecimal simpleQuestionScore = NumberUtil . mul ( questionScore , questionLength ) ;
// 循环比对这个题目,是否是复杂的题目:如选词填空或者分析题
for ( int i = 0 ; i < questionLength ; i + + ) {
String sqs = successAnswerArr [ i ] ;
boolean isTrue = allNotEmpty & & sqs . equalsIgnoreCase ( studentAnswerArr [ i ] ) ;
BigDecimal oneScore = isTrue ? simpleQuestionScore : ZERO ;
score = score . add ( oneScore ) ;
// 正确的数量计数
if ( isTrue ) {
successCount + + ;
}
}
// 全对的题目,才算做正确答案
final boolean isAllTrue = successCount = = questionLength ;
// 计算学生最后的得分
questionLog . setStudentScore ( score ) ;
questionLog . setSuccessCount ( successCount ) ;
questionLog . setErrorCount ( abs ( questionLength - successCount ) ) ;
// 错题标记
questionLog . setIsErrorFavorite ( ! isAllTrue ) ;
}
} ) ;
}
/ * *
* 功 能 描 述 : < br >
* 题 目 日 志 信 息 , 分 析 汇 总 到 某 张 表 里
*
* @param logList 某 个 questionSettingId 对 应 的 题 目 日 志 列 表
* @param questionSettingTotalCount 题 目 配 置 下 的 题 目 总 数
* @param student 学 生
* @param questionSettingName 题 目 配 置 的 名 称 ( 试 卷 名 称 )
* @param snapshotFromTypeEnum 题 目 来 源
* @Author : lx
* @Date : 2022 / 12 / 4 20 : 16
* /
public void addQuestionLogSummary (
@NotEmpty ( message = "学生题目日志不能为空!" ) List < TeacherOpenCourseQuestionLog > logList ,
@NotNull Integer questionSettingTotalCount ,
@NotNull Student student , @NotBlank String questionSettingName , @NotNull ResourcesQuestionSnapshotFromTypeEnum snapshotFromTypeEnum ) {
// 构建实体
final QuestionLogSummary questionLogSummary = new QuestionLogSummary ( ) ;
final Long questionSettingId = logList . get ( 0 ) . getTeacherOpenCourseQuestionSettingId ( ) ;
// 做对数,做错数,总题数
Integer sumSuccessCount = 0 , sumErrorCount = 0 , sumDoCount = 0 ;
// 试卷总分数, 我的分数, 题目日志ID集
BigDecimal questionTotalScore = ZERO , myTotalScore = ZERO ; StringBuilder sb = new StringBuilder ( ) ;
// 获取最大的完成时间
TeacherOpenCourseQuestionLog maxFinish = logList . stream ( )
. max ( ( o1 , o2 ) - > Math . toIntExact ( o1 . getTeacherOpenCourseQuestionLogFinishTime ( ) - o2 . getTeacherOpenCourseQuestionLogFinishTime ( ) ) )
. get ( ) ;
for ( TeacherOpenCourseQuestionLog questionLog : logList ) {
Integer successCount = questionLog . getSuccessCount ( ) ;
Integer errorCount = questionLog . getErrorCount ( ) ;
Assert . isTrue ( ObjectUtil . isAllNotEmpty ( successCount , errorCount ) , "题目正确数量和错误数量,计算出了点小问题!" ) ;
sumSuccessCount + = successCount ;
sumErrorCount + = errorCount ;
sumDoCount + = ObjectUtil . isNotEmpty ( questionLog . getTeacherOpenCourseQuestionLogAnswer ( ) ) ? 1 : 0 ;
questionTotalScore = questionTotalScore . add ( questionLog . getQuestionScore ( ) ) ;
myTotalScore = myTotalScore . add ( questionLog . getStudentScore ( ) ) ;
sb . append ( questionLog . getTeacherOpenCourseQuestionLogId ( ) ) ;
}
// 是否通过及格率 TODO 及格率要查询数据库 这里先写死
double passRateSetting = 0.6 ;
boolean isPass = NumberUtil . isGreaterOrEqual ( NumberUtil . mul ( questionTotalScore , passRateSetting ) , myTotalScore ) ;
questionLogSummary . setQuestionSettingId ( questionSettingId ) ;
questionLogSummary . setQuestionSettingName ( questionSettingName ) ;
questionLogSummary . setQuestionSettingType ( snapshotFromTypeEnum ) ;
questionLogSummary . setQuestionLogSummaryFromType ( FROM_OPEN_COURSE ) ;
questionLogSummary . setQuestionSettingTotalScore ( questionTotalScore ) ;
questionLogSummary . setPersonId ( student . getStudentId ( ) ) ;
questionLogSummary . setQuestionLogIds ( sb . toString ( ) ) ;
questionLogSummary . setQuestionLogSummaryQuestionTotalCount ( questionSettingTotalCount ) ;
questionLogSummary . setQuestionLogSummaryStudentDoCount ( sumDoCount ) ;
questionLogSummary . setQuestionLogSummarySuccessCount ( sumSuccessCount ) ;
questionLogSummary . setQuestionLogSummaryErrorCount ( sumErrorCount ) ;
questionLogSummary . setQuestionLogSummarySuccessRate ( NumberUtil . div ( sumSuccessCount , questionSettingTotalCount , 2 ) . multiply ( new BigDecimal ( 100 ) ) ) ;
questionLogSummary . setQuestionLogSummaryCurrentPassRate ( new BigDecimal ( passRateSetting ) ) ;
questionLogSummary . setQuestionLogSummaryIsPass ( BooleanUtil . toInteger ( isPass ) ) ;
questionLogSummary . setQuestionLogSummaryStatus ( 1 ) ;
questionLogSummary . setQuestionLogSummaryAddTime ( new Date ( ) ) ;
questionLogSummary . setOrgId ( student . getOrgId ( ) ) ;
questionLogSummary . setUserId ( student . getUserId ( ) ) ;
if ( null ! = maxFinish ) {
Long finishTime = maxFinish . getTeacherOpenCourseQuestionLogFinishTime ( ) ;
questionLogSummary . setFinishTime ( DateUtil . secondToTime ( finishTime . intValue ( ) ) ) ;
questionLogSummary . setFinishSecondTime ( finishTime ) ;
}
questionLogSummaryService . logicDeleteByQuestionSettingIds ( questionSettingId . toString ( ) ) ;
questionLogSummaryService . insert ( questionLogSummary ) ;
}
/ * *
* 验 证 前 端 传 递 过 来 的 添 加 时 间 是 否 是 最 新 的