@ -43,6 +43,13 @@ public class ExamService {
public static final String MULTIPLE = "MULTIPLE" ;
public static final String MULTIPLE = "MULTIPLE" ;
public static final String TRUE_FALSE = "TRUE_FALSE" ;
public static final String TRUE_FALSE = "TRUE_FALSE" ;
public static final String SHORT_ANSWER = "SHORT_ANSWER" ;
public static final String SHORT_ANSWER = "SHORT_ANSWER" ;
private static final String FIXED_ORDER = "FIXED" ;
private static final String RANDOM_ORDER = "RANDOM" ;
private static final String ALL_OR_NOTHING = "ALL_OR_NOTHING" ;
private static final String PARTIAL_BY_COUNT = "PARTIAL_BY_COUNT" ;
private static final String HALF_FOR_PARTIAL = "HALF_FOR_PARTIAL" ;
private static final Set < String > ORDER_MODES = new HashSet < > ( Arrays . asList ( FIXED_ORDER , RANDOM_ORDER ) ) ;
private static final Set < String > MULTIPLE_SCORE_MODES = new HashSet < > ( Arrays . asList ( ALL_OR_NOTHING , PARTIAL_BY_COUNT , HALF_FOR_PARTIAL ) ) ;
private static final Set < String > TYPES = new LinkedHashSet < > ( Arrays . asList ( SINGLE , MULTIPLE , TRUE_FALSE , SHORT_ANSWER ) ) ;
private static final Set < String > TYPES = new LinkedHashSet < > ( Arrays . asList ( SINGLE , MULTIPLE , TRUE_FALSE , SHORT_ANSWER ) ) ;
private static final Set < String > ALLOWED_ATTACHMENTS = new HashSet < > ( Arrays . asList ( "pdf" , "doc" , "docx" , "png" , "jpg" , "jpeg" ) ) ;
private static final Set < String > ALLOWED_ATTACHMENTS = new HashSet < > ( Arrays . asList ( "pdf" , "doc" , "docx" , "png" , "jpg" , "jpeg" ) ) ;
private static final long MAX_ATTACHMENT_SIZE = 20L * 1024 * 1024 ;
private static final long MAX_ATTACHMENT_SIZE = 20L * 1024 * 1024 ;
@ -67,22 +74,33 @@ public class ExamService {
( rs , n ) - > map ( "id" , rs . getString ( "id" ) , "name" , rs . getString ( "name" ) ) , teacher . getUserId ( ) ) ;
( rs , n ) - > map ( "id" , rs . getString ( "id" ) , "name" , rs . getString ( "name" ) ) , teacher . getUserId ( ) ) ;
}
}
public List < Map < String , Object > > questions ( JwtUser teacher , String type , String keyword) {
public List < Map < String , Object > > questions ( JwtUser teacher , String type , String taskKey, String keyword) {
requireTeacher ( teacher ) ;
requireTeacher ( teacher ) ;
StringBuilder sql = new StringBuilder ( "select * from exam_question where teacher_id=?") ;
StringBuilder sql = new StringBuilder ( "select q. *,t.project_name,t.task_name from exam_question q left join training_task t on t.task_key=q.task_key where (q.source_type='PLATFORM' or (q.source_type='TEACHER' and q. teacher_id=?)) ") ;
List < Object > args = new ArrayList < > ( ) ;
List < Object > args = new ArrayList < > ( ) ;
args . add ( teacher . getUserId ( ) ) ;
args . add ( teacher . getUserId ( ) ) ;
if ( StringUtils . isNotBlank ( type ) ) { validateType ( type ) ; sql . append ( " and question_type=?" ) ; args . add ( type ) ; }
if ( StringUtils . isNotBlank ( type ) ) { validateType ( type ) ; sql . append ( " and q.question_type=?" ) ; args . add ( type ) ; }
if ( StringUtils . isNotBlank ( keyword ) ) { sql . append ( " and stem like ?" ) ; args . add ( "%" + keyword . trim ( ) + "%" ) ; }
if ( StringUtils . isNotBlank ( taskKey ) ) { sql . append ( " and q.task_key=?" ) ; args . add ( taskKey ) ; }
sql . append ( " order by update_time desc, id desc" ) ;
if ( StringUtils . isNotBlank ( keyword ) ) { sql . append ( " and q.stem like ?" ) ; args . add ( "%" + keyword . trim ( ) + "%" ) ; }
sql . append ( " order by q.update_time desc, q.id desc" ) ;
return jdbc . query ( sql . toString ( ) , ( rs , n ) - > questionRow ( rs , true ) , args . toArray ( ) ) ;
return jdbc . query ( sql . toString ( ) , ( rs , n ) - > questionRow ( rs , true ) , args . toArray ( ) ) ;
}
}
public List < Map < String , Object > > questions ( JwtUser teacher , String type , String keyword ) {
return questions ( teacher , type , null , keyword ) ;
}
public List < Map < String , Object > > questionProjects ( JwtUser teacher ) {
requireTeacher ( teacher ) ;
return jdbc . query ( "select task_key,project_name,task_name from training_task where enabled is null or enabled=1 order by sort,project_name,task_name" ,
( rs , n ) - > map ( "taskKey" , rs . getString ( "task_key" ) , "projectName" , rs . getString ( "project_name" ) , "taskName" , rs . getString ( "task_name" ) ) ) ;
}
public Map < String , Object > createQuestion ( JwtUser teacher , Map < String , Object > payload ) {
public Map < String , Object > createQuestion ( JwtUser teacher , Map < String , Object > payload ) {
requireTeacher ( teacher ) ;
requireTeacher ( teacher ) ;
QuestionData q = validateQuestion ( payload ) ;
QuestionData q = validateQuestion ( payload ) ;
jdbc . update ( "insert into exam_question(school_id,teacher_id,question_type,stem,options_json,answer_json,answer_analysis,attachment_name,attachment_url) values(?,?,?,?,?,?,?,?,?)" ,
jdbc . update ( "insert into exam_question(school_id,teacher_id, source_type,task_key, question_type,stem,options_json,answer_json,answer_analysis,attachment_name,attachment_url) values(?,?, ?,?,?,?,?,?,?,?,?)",
teacher . getSchoolId ( ) , teacher . getUserId ( ) , q . type , q . stem , json ( q . options ) , json ( q . answer ) , q . analysis ,
teacher . getSchoolId ( ) , teacher . getUserId ( ) , "TEACHER" , text ( payload . get ( "taskKey" ) ) , q . type , q . stem , json ( q . options ) , json ( q . answer ) , q . analysis ,
text ( payload . get ( "attachmentName" ) ) , text ( payload . get ( "attachmentUrl" ) ) ) ;
text ( payload . get ( "attachmentName" ) ) , text ( payload . get ( "attachmentUrl" ) ) ) ;
Long id = jdbc . queryForObject ( "select last_insert_id()" , Long . class ) ;
Long id = jdbc . queryForObject ( "select last_insert_id()" , Long . class ) ;
return getOwnedQuestion ( teacher , id ) ;
return getOwnedQuestion ( teacher , id ) ;
@ -90,18 +108,20 @@ public class ExamService {
public Map < String , Object > updateQuestion ( JwtUser teacher , long id , Map < String , Object > payload ) {
public Map < String , Object > updateQuestion ( JwtUser teacher , long id , Map < String , Object > payload ) {
requireTeacher ( teacher ) ;
requireTeacher ( teacher ) ;
getOwnedQuestion ( teacher , id ) ;
Map < String , Object > existing = getOwnedQuestion ( teacher , id ) ;
if ( "PLATFORM" . equals ( existing . get ( "sourceType" ) ) ) throw new AccessDeniedException ( "平台题库题目只读" ) ;
QuestionData q = validateQuestion ( payload ) ;
QuestionData q = validateQuestion ( payload ) ;
jdbc . update ( "update exam_question set question_type=?,stem=?,options_json=?,answer_json=?,answer_analysis=?,attachment_name=?,attachment_url=? where id=? and teacher_id=?",
jdbc . update ( "update exam_question set task_key=?, question_type=?,stem=?,options_json=?,answer_json=?,answer_analysis=?,attachment_name=?,attachment_url=? where id=? and teacher_id=? and source_type='TEACHER' ",
q. type , q . stem , json ( q . options ) , json ( q . answer ) , q . analysis ,
text( payload . get ( "taskKey" ) ) , q. type , q . stem , json ( q . options ) , json ( q . answer ) , q . analysis ,
text ( payload . get ( "attachmentName" ) ) , text ( payload . get ( "attachmentUrl" ) ) , id , teacher . getUserId ( ) ) ;
text ( payload . get ( "attachmentName" ) ) , text ( payload . get ( "attachmentUrl" ) ) , id , teacher . getUserId ( ) ) ;
return getOwnedQuestion ( teacher , id ) ;
return getOwnedQuestion ( teacher , id ) ;
}
}
public void deleteQuestion ( JwtUser teacher , long id ) {
public void deleteQuestion ( JwtUser teacher , long id ) {
requireTeacher ( teacher ) ;
requireTeacher ( teacher ) ;
getOwnedQuestion ( teacher , id ) ;
Map < String , Object > existing = getOwnedQuestion ( teacher , id ) ;
jdbc . update ( "delete from exam_question where id=? and teacher_id=?" , id , teacher . getUserId ( ) ) ;
if ( "PLATFORM" . equals ( existing . get ( "sourceType" ) ) ) throw new AccessDeniedException ( "平台题库题目只读" ) ;
jdbc . update ( "delete from exam_question where id=? and teacher_id=? and source_type='TEACHER'" , id , teacher . getUserId ( ) ) ;
}
}
public Map < String , Object > uploadQuestionAttachment ( JwtUser teacher , MultipartFile file ) {
public Map < String , Object > uploadQuestionAttachment ( JwtUser teacher , MultipartFile file ) {
@ -263,13 +283,18 @@ public class ExamService {
id = = null ? new Object [ ] { teacher . getSchoolId ( ) , name } : new Object [ ] { teacher . getSchoolId ( ) , name , id } ) ;
id = = null ? new Object [ ] { teacher . getSchoolId ( ) , name } : new Object [ ] { teacher . getSchoolId ( ) , name , id } ) ;
if ( duplicate > 0 ) throw new IllegalArgumentException ( "考试名称已存在" ) ;
if ( duplicate > 0 ) throw new IllegalArgumentException ( "考试名称已存在" ) ;
boolean allowRollback = bool ( payload . get ( "allowRollback" ) , true ) ;
boolean allowRollback = bool ( payload . get ( "allowRollback" ) , true ) ;
String questionOrderMode = enumValue ( payload . get ( "questionOrderMode" ) , FIXED_ORDER , ORDER_MODES , "题目乱序方式无效" ) ;
String optionOrderMode = enumValue ( payload . get ( "optionOrderMode" ) , FIXED_ORDER , ORDER_MODES , "选项乱序方式无效" ) ;
boolean allowCopyQuestion = bool ( payload . get ( "allowCopyQuestion" ) , false ) ;
boolean allowPasteAnswer = bool ( payload . get ( "allowPasteAnswer" ) , false ) ;
String multipleScoreMode = enumValue ( payload . get ( "multipleScoreMode" ) , ALL_OR_NOTHING , MULTIPLE_SCORE_MODES , "多选题计分方式无效" ) ;
long examId ;
long examId ;
if ( id = = null ) {
if ( id = = null ) {
jdbc . update ( "insert into exam_assessment(school_id,teacher_id,exam_name,start_time,end_time,allow_rollback) values(?,?,?,?,?,?)" ,
jdbc . update ( "insert into exam_assessment(school_id,teacher_id,exam_name,start_time,end_time,allow_rollback ,question_order_mode,option_order_mode,allow_copy_question,allow_paste_answer,multiple_score_mode ) values(?,?,?,?,?, ?,?,?,?,?,?)",
teacher . getSchoolId ( ) , teacher . getUserId ( ) , name , start , end , allowRollback ) ;
teacher . getSchoolId ( ) , teacher . getUserId ( ) , name , start , end , allowRollback , questionOrderMode , optionOrderMode , allowCopyQuestion , allowPasteAnswer , multipleScoreMode );
examId = jdbc . queryForObject ( "select last_insert_id()" , Long . class ) ;
examId = jdbc . queryForObject ( "select last_insert_id()" , Long . class ) ;
} else {
} else {
jdbc . update ( "update exam_assessment set exam_name=?,start_time=?,end_time=?,allow_rollback=? where id=?", name , start , end , allowRollback , id ) ;
jdbc . update ( "update exam_assessment set exam_name=?,start_time=?,end_time=?,allow_rollback=? ,question_order_mode=?,option_order_mode=?,allow_copy_question=?,allow_paste_answer=?,multiple_score_mode=? where id=?", name , start , end , allowRollback , questionOrderMode , optionOrderMode , allowCopyQuestion , allowPasteAnswer , multipleScoreMode , id ) ;
jdbc . update ( "delete from exam_assessment_class where exam_id=?" , id ) ;
jdbc . update ( "delete from exam_assessment_class where exam_id=?" , id ) ;
jdbc . update ( "delete from exam_assessment_question where exam_id=?" , id ) ;
jdbc . update ( "delete from exam_assessment_question where exam_id=?" , id ) ;
examId = id ;
examId = id ;
@ -440,7 +465,10 @@ public class ExamService {
public List < Map < String , Object > > studentExams ( JwtUser student ) {
public List < Map < String , Object > > studentExams ( JwtUser student ) {
requireStudent ( student ) ; autoSubmitExpired ( ) ;
requireStudent ( student ) ; autoSubmitExpired ( ) ;
return jdbc . query ( "select distinct e.*,a.id attempt_id,a.attempt_status,a.total_score from exam_assessment e " +
return jdbc . query ( "select distinct e.*,a.id attempt_id,a.attempt_status,a.total_score," +
"(select u.name from userinfo u where u.user_id=e.teacher_id collate utf8mb4_general_ci limit 1) teacher_name," +
"(select group_concat(distinct s.class_name order by s.class_name separator '、') from exam_assessment_class ec join school_class s on s.school_class_id=ec.teaching_class_id collate utf8mb4_0900_ai_ci where ec.exam_id=e.id) class_names " +
"from exam_assessment e " +
"join exam_assessment_class c on c.exam_id=e.id join teaching_class_student t on t.teaching_class_id=c.teaching_class_id and t.status='IN_PROGRESS' " +
"join exam_assessment_class c on c.exam_id=e.id join teaching_class_student t on t.teaching_class_id=c.teaching_class_id and t.status='IN_PROGRESS' " +
"left join exam_attempt a on a.exam_id=e.id and a.student_id=? where t.student_user_id=? and e.publication_status='PUBLISHED' order by e.start_time desc" ,
"left join exam_attempt a on a.exam_id=e.id and a.student_id=? where t.student_user_id=? and e.publication_status='PUBLISHED' order by e.start_time desc" ,
( rs , n ) - > { Map < String , Object > m = examSummary ( rs ) ; m . put ( "attemptId" , rs . getObject ( "attempt_id" ) ) ; m . put ( "attemptStatus" , rs . getString ( "attempt_status" ) ) ;
( rs , n ) - > { Map < String , Object > m = examSummary ( rs ) ; m . put ( "attemptId" , rs . getObject ( "attempt_id" ) ) ; m . put ( "attemptStatus" , rs . getString ( "attempt_status" ) ) ;
@ -480,13 +508,27 @@ public class ExamService {
if ( ! "IN_PROGRESS" . equals ( exam . get ( "status" ) ) ) throw new IllegalArgumentException ( "考试已结束,不能继续作答" ) ;
if ( ! "IN_PROGRESS" . equals ( exam . get ( "status" ) ) ) throw new IllegalArgumentException ( "考试已结束,不能继续作答" ) ;
long attemptId = requireAttempt ( student , examId , deviceToken , true ) ;
long attemptId = requireAttempt ( student , examId , deviceToken , true ) ;
Map < String , Object > question = examQuestion ( examId , questionId , true ) ;
Map < String , Object > question = examQuestion ( examId , questionId , true ) ;
Boolean correct = SHORT_ANSWER . equals ( question . get ( "type" ) ) ? null : answersEqual ( question . get ( "answer" ) , answer ) ;
String type = String . valueOf ( question . get ( "type" ) ) ;
BigDecimal score = correct = = null ? null : ( correct ? decimal ( question . get ( "score" ) ) : BigDecimal . ZERO ) ;
Boolean correct = SHORT_ANSWER . equals ( type ) ? null : answersEqual ( question . get ( "answer" ) , answer ) ;
BigDecimal score = SHORT_ANSWER . equals ( type ) ? null : objectiveScore ( exam , question , answer ) ;
jdbc . update ( "insert into exam_attempt_answer(attempt_id,question_id,answer_json,is_correct,awarded_score) values(?,?,?,?,?) " +
jdbc . update ( "insert into exam_attempt_answer(attempt_id,question_id,answer_json,is_correct,awarded_score) values(?,?,?,?,?) " +
"on duplicate key update answer_json=values(answer_json),is_correct=values(is_correct),awarded_score=values(awarded_score)" ,
"on duplicate key update answer_json=values(answer_json),is_correct=values(is_correct),awarded_score=values(awarded_score)" ,
attemptId , questionId , json ( answer ) , correct , score ) ;
attemptId , questionId , json ( answer ) , correct , score ) ;
}
}
private BigDecimal objectiveScore ( Map < String , Object > exam , Map < String , Object > question , Object answer ) {
BigDecimal full = decimal ( question . get ( "score" ) ) ;
if ( ! MULTIPLE . equals ( question . get ( "type" ) ) ) return answersEqual ( question . get ( "answer" ) , answer ) ? full : BigDecimal . ZERO ;
Set < String > expected = toSet ( question . get ( "answer" ) ) ;
Set < String > actual = toSet ( answer ) ;
if ( expected . isEmpty ( ) | | actual . isEmpty ( ) | | ! expected . containsAll ( actual ) ) return BigDecimal . ZERO ;
if ( expected . equals ( actual ) ) return full ;
String mode = String . valueOf ( exam . getOrDefault ( "multipleScoreMode" , ALL_OR_NOTHING ) ) ;
if ( PARTIAL_BY_COUNT . equals ( mode ) ) return full . multiply ( BigDecimal . valueOf ( actual . size ( ) ) ) . divide ( BigDecimal . valueOf ( expected . size ( ) ) , 2 , BigDecimal . ROUND_HALF_UP ) ;
if ( HALF_FOR_PARTIAL . equals ( mode ) ) return full . divide ( BigDecimal . valueOf ( 2 ) , 2 , BigDecimal . ROUND_HALF_UP ) ;
return BigDecimal . ZERO ;
}
public Map < String , Object > uploadAnswerFile ( JwtUser student , long examId , long questionId , String deviceToken , MultipartFile file ) {
public Map < String , Object > uploadAnswerFile ( JwtUser student , long examId , long questionId , String deviceToken , MultipartFile file ) {
requireStudent ( student ) ; validateAttachment ( file ) ;
requireStudent ( student ) ; validateAttachment ( file ) ;
Map < String , Object > exam = accessibleStudentExam ( student , examId ) ;
Map < String , Object > exam = accessibleStudentExam ( student , examId ) ;
@ -561,6 +603,7 @@ public class ExamService {
private List < Map < String , Object > > answeredQuestions ( long examId , long attemptId , boolean revealAnswer ) {
private List < Map < String , Object > > answeredQuestions ( long examId , long attemptId , boolean revealAnswer ) {
List < Map < String , Object > > questions = examQuestions ( examId , revealAnswer ) ;
List < Map < String , Object > > questions = examQuestions ( examId , revealAnswer ) ;
if ( ! revealAnswer ) applyStudentOrdering ( examId , attemptId , questions ) ;
Map < Long , Map < String , Object > > answers = jdbc . query ( "select * from exam_attempt_answer where attempt_id=?" , ( rs , n ) - > {
Map < Long , Map < String , Object > > answers = jdbc . query ( "select * from exam_attempt_answer where attempt_id=?" , ( rs , n ) - > {
Map < String , Object > m = map ( "questionId" , rs . getLong ( "question_id" ) , "studentAnswer" , parse ( rs . getString ( "answer_json" ) ) ,
Map < String , Object > m = map ( "questionId" , rs . getLong ( "question_id" ) , "studentAnswer" , parse ( rs . getString ( "answer_json" ) ) ,
"correct" , rs . getObject ( "is_correct" ) = = null ? null : rs . getBoolean ( "is_correct" ) , "awardedScore" , rs . getBigDecimal ( "awarded_score" ) ,
"correct" , rs . getObject ( "is_correct" ) = = null ? null : rs . getBoolean ( "is_correct" ) , "awardedScore" , rs . getBigDecimal ( "awarded_score" ) ,
@ -570,6 +613,32 @@ public class ExamService {
return questions ;
return questions ;
}
}
private void applyStudentOrdering ( long examId , long attemptId , List < Map < String , Object > > questions ) {
Map < String , Object > exam = jdbc . queryForMap ( "select question_order_mode,option_order_mode from exam_assessment where id=?" , examId ) ;
String questionMode = String . valueOf ( exam . getOrDefault ( "question_order_mode" , FIXED_ORDER ) ) ;
String optionMode = String . valueOf ( exam . getOrDefault ( "option_order_mode" , FIXED_ORDER ) ) ;
if ( RANDOM_ORDER . equals ( questionMode ) ) {
Map < String , List < Map < String , Object > > > groups = new LinkedHashMap < > ( ) ;
for ( Map < String , Object > question : questions ) groups . computeIfAbsent ( String . valueOf ( question . get ( "type" ) ) , key - > new ArrayList < > ( ) ) . add ( question ) ;
List < Map < String , Object > > reordered = new ArrayList < > ( ) ;
for ( List < Map < String , Object > > group : groups . values ( ) ) {
Collections . shuffle ( group , new Random ( attemptId * 31L + group . hashCode ( ) ) ) ;
reordered . addAll ( group ) ;
}
questions . clear ( ) ; questions . addAll ( reordered ) ;
for ( int i = 0 ; i < questions . size ( ) ; i + + ) questions . get ( i ) . put ( "order" , i + 1 ) ;
}
if ( RANDOM_ORDER . equals ( optionMode ) ) {
for ( Map < String , Object > question : questions ) {
List < Map < String , Object > > options = mapList ( question . get ( "options" ) ) ;
if ( options . size ( ) > 1 ) {
Collections . shuffle ( options , new Random ( attemptId * 131L + ( ( Number ) question . get ( "id" ) ) . longValue ( ) ) ) ;
question . put ( "options" , options ) ;
}
}
}
}
private List < Map < String , Object > > examQuestions ( long examId , boolean revealAnswer ) {
private List < Map < String , Object > > examQuestions ( long examId , boolean revealAnswer ) {
return jdbc . query ( "select p.question_id id,p.question_type,p.stem,p.options_json,p.answer_json,p.answer_analysis,p.attachment_name,p.attachment_url,p.create_time,p.update_time,p.question_score,p.question_order " +
return jdbc . query ( "select p.question_id id,p.question_type,p.stem,p.options_json,p.answer_json,p.answer_analysis,p.attachment_name,p.attachment_url,p.create_time,p.update_time,p.question_score,p.question_order " +
"from exam_assessment_question p where p.exam_id=? order by p.question_order" ,
"from exam_assessment_question p where p.exam_id=? order by p.question_order" ,
@ -583,7 +652,7 @@ public class ExamService {
}
}
private Map < String , Object > getOwnedQuestion ( JwtUser teacher , long id ) {
private Map < String , Object > getOwnedQuestion ( JwtUser teacher , long id ) {
List < Map < String , Object > > rows = jdbc . query ( "select * from exam_question where id=? and teacher_id=?", ( rs , n ) - > questionRow ( rs , true ) , id , teacher . getUserId ( ) ) ;
List < Map < String , Object > > rows = jdbc . query ( "select q. *,t.project_name,t.task_name from exam_question q left join training_task t on t.task_key=q.task_key where q. id=? and (q.source_type='PLATFORM' or (q.source_type='TEACHER' and q. teacher_id=?)) ", ( rs , n ) - > questionRow ( rs , true ) , id , teacher . getUserId ( ) ) ;
if ( rows . isEmpty ( ) ) throw new IllegalArgumentException ( "题目不存在或无权访问" ) ;
if ( rows . isEmpty ( ) ) throw new IllegalArgumentException ( "题目不存在或无权访问" ) ;
return rows . get ( 0 ) ;
return rows . get ( 0 ) ;
}
}
@ -630,6 +699,14 @@ public class ExamService {
"startTime" , start , "endTime" , end , "durationMinutes" , Math . max ( 0 , ( end . getTime ( ) - start . getTime ( ) ) / 60000 ) ,
"startTime" , start , "endTime" , end , "durationMinutes" , Math . max ( 0 , ( end . getTime ( ) - start . getTime ( ) ) / 60000 ) ,
"publicationStatus" , publication , "status" , status ( publication , start , end ) , "allowRollback" , rs . getBoolean ( "allow_rollback" ) ,
"publicationStatus" , publication , "status" , status ( publication , start , end ) , "allowRollback" , rs . getBoolean ( "allow_rollback" ) ,
"exportedAt" , getQuiet ( rs , "exported_at" ) , "totalScore" , getQuiet ( rs , "total_score" ) ) ;
"exportedAt" , getQuiet ( rs , "exported_at" ) , "totalScore" , getQuiet ( rs , "total_score" ) ) ;
putQuiet ( rs , m , "question_order_mode" , "questionOrderMode" ) ;
putQuiet ( rs , m , "option_order_mode" , "optionOrderMode" ) ;
Object copyQuestion = getQuiet ( rs , "allow_copy_question" ) ; if ( copyQuestion ! = null ) m . put ( "allowCopyQuestion" , copyQuestion instanceof Boolean ? copyQuestion : rsBoolean ( copyQuestion ) ) ;
Object pasteAnswer = getQuiet ( rs , "allow_paste_answer" ) ; if ( pasteAnswer ! = null ) m . put ( "allowPasteAnswer" , pasteAnswer instanceof Boolean ? pasteAnswer : rsBoolean ( pasteAnswer ) ) ;
putQuiet ( rs , m , "multiple_score_mode" , "multipleScoreMode" ) ;
m . putIfAbsent ( "questionOrderMode" , FIXED_ORDER ) ; m . putIfAbsent ( "optionOrderMode" , FIXED_ORDER ) ;
m . putIfAbsent ( "allowCopyQuestion" , false ) ; m . putIfAbsent ( "allowPasteAnswer" , false ) ; m . putIfAbsent ( "multipleScoreMode" , ALL_OR_NOTHING ) ;
putQuiet ( rs , m , "teacher_name" , "teacherName" ) ; putQuiet ( rs , m , "class_names" , "classNames" ) ;
putQuiet ( rs , m , "class_count" , "classCount" ) ; putQuiet ( rs , m , "question_count" , "questionCount" ) ;
putQuiet ( rs , m , "class_count" , "classCount" ) ; putQuiet ( rs , m , "question_count" , "questionCount" ) ;
return m ;
return m ;
}
}
@ -666,6 +743,8 @@ public class ExamService {
"options" , parse ( rs . getString ( "options_json" ) ) , "analysis" , revealAnswer ? rs . getString ( "answer_analysis" ) : null ,
"options" , parse ( rs . getString ( "options_json" ) ) , "analysis" , revealAnswer ? rs . getString ( "answer_analysis" ) : null ,
"attachmentName" , rs . getString ( "attachment_name" ) , "attachmentUrl" , rs . getString ( "attachment_url" ) ,
"attachmentName" , rs . getString ( "attachment_name" ) , "attachmentUrl" , rs . getString ( "attachment_url" ) ,
"createdAt" , rs . getTimestamp ( "create_time" ) , "updatedAt" , rs . getTimestamp ( "update_time" ) ) ;
"createdAt" , rs . getTimestamp ( "create_time" ) , "updatedAt" , rs . getTimestamp ( "update_time" ) ) ;
putQuiet ( rs , q , "source_type" , "sourceType" ) ; putQuiet ( rs , q , "task_key" , "taskKey" ) ;
putQuiet ( rs , q , "project_name" , "projectName" ) ; putQuiet ( rs , q , "task_name" , "taskName" ) ;
if ( revealAnswer ) q . put ( "answer" , parse ( rs . getString ( "answer_json" ) ) ) ;
if ( revealAnswer ) q . put ( "answer" , parse ( rs . getString ( "answer_json" ) ) ) ;
return q ;
return q ;
}
}
@ -1099,12 +1178,22 @@ public class ExamService {
private static Number number ( Object value ) { if ( value instanceof Number ) return ( Number ) value ; try { return Long . parseLong ( text ( value ) ) ; } catch ( Exception e ) { throw new IllegalArgumentException ( "数字参数格式错误" ) ; } }
private static Number number ( Object value ) { if ( value instanceof Number ) return ( Number ) value ; try { return Long . parseLong ( text ( value ) ) ; } catch ( Exception e ) { throw new IllegalArgumentException ( "数字参数格式错误" ) ; } }
private static BigDecimal decimal ( Object value ) { if ( value = = null | | text ( value ) . isEmpty ( ) ) return BigDecimal . ZERO ; return value instanceof BigDecimal ? ( BigDecimal ) value : new BigDecimal ( text ( value ) ) ; }
private static BigDecimal decimal ( Object value ) { if ( value = = null | | text ( value ) . isEmpty ( ) ) return BigDecimal . ZERO ; return value instanceof BigDecimal ? ( BigDecimal ) value : new BigDecimal ( text ( value ) ) ; }
private static boolean bool ( Object value , boolean fallback ) { return value = = null ? fallback : Boolean . parseBoolean ( text ( value ) ) ; }
private static boolean bool ( Object value , boolean fallback ) { return value = = null ? fallback : Boolean . parseBoolean ( text ( value ) ) ; }
private static String enumValue ( Object value , String fallback , Set < String > allowed , String message ) {
String normalized = StringUtils . defaultIfBlank ( text ( value ) , fallback ) . trim ( ) . toUpperCase ( Locale . ROOT ) ;
if ( ! allowed . contains ( normalized ) ) throw new IllegalArgumentException ( message ) ;
return normalized ;
}
private static Timestamp parseTime ( Object value ) { try { String s = text ( value ) . replace ( 'T' , ' ' ) ; if ( s . length ( ) = = 16 ) s + = ":00" ; return Timestamp . valueOf ( LocalDateTime . parse ( s , TIME ) ) ; } catch ( Exception e ) { throw new IllegalArgumentException ( "考试时间格式不正确" ) ; } }
private static Timestamp parseTime ( Object value ) { try { String s = text ( value ) . replace ( 'T' , ' ' ) ; if ( s . length ( ) = = 16 ) s + = ":00" ; return Timestamp . valueOf ( LocalDateTime . parse ( s , TIME ) ) ; } catch ( Exception e ) { throw new IllegalArgumentException ( "考试时间格式不正确" ) ; } }
private String json ( Object value ) { try { return objectMapper . writeValueAsString ( value ) ; } catch ( Exception e ) { throw new IllegalArgumentException ( "数据格式错误" ) ; } }
private String json ( Object value ) { try { return objectMapper . writeValueAsString ( value ) ; } catch ( Exception e ) { throw new IllegalArgumentException ( "数据格式错误" ) ; } }
private Object parse ( String value ) { if ( StringUtils . isBlank ( value ) ) return null ; try { return objectMapper . readValue ( value , Object . class ) ; } catch ( Exception e ) { return value ; } }
private Object parse ( String value ) { if ( StringUtils . isBlank ( value ) ) return null ; try { return objectMapper . readValue ( value , Object . class ) ; } catch ( Exception e ) { return value ; } }
private List < Map < String , Object > > mapList ( Object value ) { if ( value = = null ) return new ArrayList < > ( ) ; return objectMapper . convertValue ( value , new TypeReference < List < Map < String , Object > > > ( ) { } ) ; }
private List < Map < String , Object > > mapList ( Object value ) { if ( value = = null ) return new ArrayList < > ( ) ; return objectMapper . convertValue ( value , new TypeReference < List < Map < String , Object > > > ( ) { } ) ; }
private List < String > stringList ( Object value ) { if ( value = = null ) return new ArrayList < > ( ) ; if ( value instanceof Collection ) return ( ( Collection < ? > ) value ) . stream ( ) . map ( String : : valueOf ) . filter ( StringUtils : : isNotBlank ) . collect ( Collectors . toList ( ) ) ; String s = text ( value ) ; return StringUtils . isBlank ( s ) ? new ArrayList < > ( ) : Collections . singletonList ( s ) ; }
private List < String > stringList ( Object value ) { if ( value = = null ) return new ArrayList < > ( ) ; if ( value instanceof Collection ) return ( ( Collection < ? > ) value ) . stream ( ) . map ( String : : valueOf ) . filter ( StringUtils : : isNotBlank ) . collect ( Collectors . toList ( ) ) ; String s = text ( value ) ; return StringUtils . isBlank ( s ) ? new ArrayList < > ( ) : Collections . singletonList ( s ) ; }
private static Object getQuiet ( ResultSet rs , String column ) { try { return rs . getObject ( column ) ; } catch ( SQLException e ) { return null ; } }
private static Object getQuiet ( ResultSet rs , String column ) { try { return rs . getObject ( column ) ; } catch ( SQLException e ) { return null ; } }
private static boolean rsBoolean ( Object value ) {
if ( value instanceof byte [ ] ) return ( ( byte [ ] ) value ) . length > 0 & & ( ( byte [ ] ) value ) [ 0 ] ! = 0 ;
if ( value instanceof Number ) return ( ( Number ) value ) . intValue ( ) ! = 0 ;
return Boolean . parseBoolean ( String . valueOf ( value ) ) ;
}
private static void putQuiet ( ResultSet rs , Map < String , Object > map , String column , String key ) { Object value = getQuiet ( rs , column ) ; if ( value ! = null ) map . put ( key , value ) ; }
private static void putQuiet ( ResultSet rs , Map < String , Object > map , String column , String key ) { Object value = getQuiet ( rs , column ) ; if ( value ! = null ) map . put ( key , value ) ; }
private static Map < String , Object > map ( Object . . . values ) { Map < String , Object > m = new LinkedHashMap < > ( ) ; for ( int i = 0 ; i < values . length ; i + = 2 ) m . put ( String . valueOf ( values [ i ] ) , values [ i + 1 ] ) ; return m ; }
private static Map < String , Object > map ( Object . . . values ) { Map < String , Object > m = new LinkedHashMap < > ( ) ; for ( int i = 0 ; i < values . length ; i + = 2 ) m . put ( String . valueOf ( values [ i ] ) , values [ i + 1 ] ) ; return m ; }
private static boolean answersEqual ( Object expected , Object actual ) { if ( expected instanceof Collection | | actual instanceof Collection ) { Set < String > a = toSet ( expected ) , b = toSet ( actual ) ; return a . equals ( b ) ; } return String . valueOf ( expected ) . equalsIgnoreCase ( String . valueOf ( actual ) ) ; }
private static boolean answersEqual ( Object expected , Object actual ) { if ( expected instanceof Collection | | actual instanceof Collection ) { Set < String > a = toSet ( expected ) , b = toSet ( actual ) ; return a . equals ( b ) ; } return String . valueOf ( expected ) . equalsIgnoreCase ( String . valueOf ( actual ) ) ; }