package com.sztzjy.linkCommerce.service.impl; import com.sztzjy.linkCommerce.config.exception.handler.ServiceException; import com.sztzjy.linkCommerce.config.security.JwtUser; import com.sztzjy.linkCommerce.entity.StudentTrainingAnswer; import com.sztzjy.linkCommerce.entity.TeachingClassStudent; import com.sztzjy.linkCommerce.entity.TrainingTask; import com.sztzjy.linkCommerce.mapper.StudentTrainingAnswerMapper; import com.sztzjy.linkCommerce.mapper.TeachingClassStudentMapper; import com.sztzjy.linkCommerce.mapper.TrainingTaskMapper; import com.sztzjy.linkCommerce.service.StudentTrainingAnswerService; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.UUID; @Service public class StudentTrainingAnswerServiceImpl implements StudentTrainingAnswerService { private static final String STATUS_NOT_STARTED = "NOT_STARTED"; private static final String STATUS_IN_PROGRESS = "IN_PROGRESS"; private static final String STATUS_COMPLETED = "COMPLETED"; private static final String ACTION_SAVE = "SAVE"; private static final String ACTION_SUBMIT = "SUBMIT"; private static final String ACTION_RESET = "RESET"; @Autowired public StudentTrainingAnswerMapper studentTrainingAnswerMapper; @Autowired public TrainingTaskMapper trainingTaskMapper; @Autowired public TeachingClassStudentMapper teachingClassStudentMapper; @Autowired(required = false) public JdbcTemplate jdbcTemplate; private boolean studentTrainingAnswerTableChecked = false; @Override public StudentTrainingAnswer get(String taskKey, String teachingClassId, JwtUser user) { ensureStudentTrainingAnswerTable(); requireStudent(user); TrainingTask task = resolveTask(taskKey); String resolvedTeachingClassId = resolveTeachingClassId(user.getUserId(), teachingClassId); return studentTrainingAnswerMapper.selectByStudentClassAndTask( user.getUserId(), resolvedTeachingClassId, task.getId()); } @Override @Transactional(rollbackFor = Exception.class) public StudentTrainingAnswer save(String taskKey, StudentTrainingAnswer answer, JwtUser user) { ensureStudentTrainingAnswerTable(); requireStudent(user); TrainingTask task = resolveTask(taskKey); String resolvedTeachingClassId = resolveTeachingClassId(user.getUserId(), answer == null ? null : answer.getTeachingClassId()); StudentTrainingAnswer existing = studentTrainingAnswerMapper.selectByStudentClassAndTask( user.getUserId(), resolvedTeachingClassId, task.getId()); if (!isPersistAction(answer)) { return existing; } StudentTrainingAnswer record = buildRecord(answer, existing, user.getUserId(), resolvedTeachingClassId, task); if (existing == null) { record.setId(UUID.randomUUID().toString()); record.setCreateTime(new Date()); record.setUpdateTime(new Date()); studentTrainingAnswerMapper.insertSelective(record); } else { record.setId(existing.getId()); record.setCreateTime(existing.getCreateTime()); record.setUpdateTime(new Date()); studentTrainingAnswerMapper.updateByPrimaryKeySelective(record); } StudentTrainingAnswer saved = studentTrainingAnswerMapper.selectByStudentClassAndTask( user.getUserId(), resolvedTeachingClassId, task.getId()); syncDetailTables(saved); return saved; } @Override @Transactional(rollbackFor = Exception.class) public void delete(String taskKey, String teachingClassId, JwtUser user) { ensureStudentTrainingAnswerTable(); requireStudent(user); TrainingTask task = resolveTask(taskKey); String resolvedTeachingClassId = resolveTeachingClassId(user.getUserId(), teachingClassId); studentTrainingAnswerMapper.deleteByStudentClassAndTask(user.getUserId(), resolvedTeachingClassId, task.getId()); if (jdbcTemplate != null) { jdbcTemplate.update("delete from student_training_step_answer where student_user_id = ? and teaching_class_id = ? and task_id = ?", user.getUserId(), resolvedTeachingClassId, task.getId()); jdbcTemplate.update("delete from student_training_progress where student_user_id = ? and teaching_class_id = ? and task_id = ?", user.getUserId(), resolvedTeachingClassId, task.getId()); } } private void requireStudent(JwtUser user) { if (user == null || StringUtils.isBlank(user.getUserId())) { throw new ServiceException(HttpStatus.UNAUTHORIZED, "Please login first"); } if (user.getRoleId() != 4) { throw new ServiceException(HttpStatus.FORBIDDEN, "Only students can submit training answers"); } } private TrainingTask resolveTask(String taskKey) { String normalizedTaskKey = StringUtils.trimToEmpty(taskKey); if (StringUtils.isBlank(normalizedTaskKey)) { throw new ServiceException(HttpStatus.BAD_REQUEST, "Training task key is required"); } TrainingTask task = trainingTaskMapper.selectByTaskKey(normalizedTaskKey); if (task == null) { throw new ServiceException(HttpStatus.BAD_REQUEST, "Training task does not exist"); } return task; } private String resolveTeachingClassId(String studentUserId, String requestedTeachingClassId) { String trimmedClassId = StringUtils.trimToNull(requestedTeachingClassId); if (trimmedClassId != null) { long count = teachingClassStudentMapper.countByStudentUserIdAndTeachingClassId(studentUserId, trimmedClassId); if (count <= 0) { throw new ServiceException(HttpStatus.BAD_REQUEST, "Student does not belong to this teaching class"); } return trimmedClassId; } TeachingClassStudent member = teachingClassStudentMapper.selectActiveByStudentUserId(studentUserId); if (member == null || StringUtils.isBlank(member.getTeachingClassId())) { throw new ServiceException(HttpStatus.BAD_REQUEST, "Student has no active teaching class"); } return member.getTeachingClassId(); } private StudentTrainingAnswer buildRecord(StudentTrainingAnswer request, StudentTrainingAnswer existing, String studentUserId, String teachingClassId, TrainingTask task) { StudentTrainingAnswer record = new StudentTrainingAnswer(); record.setStudentUserId(studentUserId); record.setTeachingClassId(teachingClassId); record.setTaskId(task.getId()); record.setTaskKey(task.getTaskKey()); record.setTaskName(task.getTaskName()); if (existing != null) { record.setStep1Answer(existing.getStep1Answer()); record.setStep2Answer(existing.getStep2Answer()); record.setStep3Answer(existing.getStep3Answer()); record.setStep4Answer(existing.getStep4Answer()); record.setSubmitTime(existing.getSubmitTime()); } if (isResetAction(request)) { record.setStep1Answer(""); record.setStep2Answer(""); record.setStep3Answer(""); record.setStep4Answer(""); record.setCurrentStep(1); record.setProgressStatus(STATUS_NOT_STARTED); record.setSubmitted(Boolean.FALSE); record.setSubmitTime(null); return record; } if (request.getStep1Answer() != null) { record.setStep1Answer(request.getStep1Answer()); } if (request.getStep2Answer() != null) { record.setStep2Answer(request.getStep2Answer()); } if (request.getStep3Answer() != null) { record.setStep3Answer(request.getStep3Answer()); } if (request.getStep4Answer() != null) { record.setStep4Answer(request.getStep4Answer()); } boolean submitted = ACTION_SUBMIT.equals(StringUtils.upperCase(StringUtils.trimToEmpty(request.getSaveAction()))); record.setSubmitted(submitted); record.setCurrentStep(submitted ? 4 : normalizeCurrentStepValue(request.getCurrentStep())); record.setProgressStatus(submitted ? STATUS_COMPLETED : resolveProgressStatus(record)); if (submitted) { record.setSubmitTime(new Date()); } return record; } private Integer normalizeCurrentStepValue(Integer currentStep) { if (currentStep == null || currentStep < 1) { return 1; } if (currentStep > 4) { return 4; } return currentStep; } private String resolveProgressStatus(StudentTrainingAnswer answer) { if (StringUtils.isNotBlank(answer.getStep1Answer()) || StringUtils.isNotBlank(answer.getStep2Answer()) || StringUtils.isNotBlank(answer.getStep3Answer()) || StringUtils.isNotBlank(answer.getStep4Answer())) { return STATUS_IN_PROGRESS; } return STATUS_NOT_STARTED; } private boolean isResetAction(StudentTrainingAnswer answer) { return answer != null && ACTION_RESET.equals(StringUtils.upperCase(StringUtils.trimToEmpty(answer.getSaveAction()))); } private boolean isPersistAction(StudentTrainingAnswer answer) { if (answer == null) { return false; } String action = StringUtils.upperCase(StringUtils.trimToEmpty(answer.getSaveAction())); return ACTION_SAVE.equals(action) || ACTION_SUBMIT.equals(action) || ACTION_RESET.equals(action); } private void ensureStudentTrainingAnswerTable() { if (studentTrainingAnswerTableChecked || jdbcTemplate == null) { return; } jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS student_training_answer (" + "id varchar(64) NOT NULL COMMENT 'primary id'," + "student_user_id varchar(64) NOT NULL COMMENT 'student user id'," + "teaching_class_id varchar(64) NOT NULL COMMENT 'teaching class id'," + "task_id varchar(64) NOT NULL COMMENT 'training task id'," + "task_key varchar(128) NOT NULL COMMENT 'training task key'," + "task_name varchar(128) NOT NULL COMMENT 'training task name'," + "step1_answer longtext NULL COMMENT 'step 1 answer json'," + "step2_answer longtext NULL COMMENT 'step 2 answer json'," + "step3_answer longtext NULL COMMENT 'step 3 answer json'," + "step4_answer longtext NULL COMMENT 'step 4 answer json'," + "current_step int DEFAULT 1 COMMENT 'current step'," + "progress_status varchar(32) DEFAULT 'NOT_STARTED' COMMENT 'progress status'," + "submitted bit(1) DEFAULT b'0' COMMENT 'submitted'," + "ai_assessment_score int NULL COMMENT 'AI assessment score'," + "submit_time datetime NULL COMMENT 'submit time'," + "create_time datetime NULL COMMENT 'create time'," + "update_time datetime NULL COMMENT 'update time'," + "PRIMARY KEY (id)," + "KEY idx_student_training_answer_task (task_id)," + "KEY idx_student_training_answer_student (student_user_id)," + "KEY idx_student_training_answer_class (teaching_class_id)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='student training answer'"); jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS student_training_progress (" + "id varchar(64) NOT NULL COMMENT 'primary id'," + "student_user_id varchar(64) NOT NULL COMMENT 'student user id'," + "teaching_class_id varchar(64) NOT NULL COMMENT 'teaching class id'," + "task_id varchar(64) NOT NULL COMMENT 'training task id'," + "task_key varchar(128) NOT NULL COMMENT 'training task key'," + "task_name varchar(128) NOT NULL COMMENT 'training task name'," + "current_step int DEFAULT 1 COMMENT 'current step'," + "progress_status varchar(32) DEFAULT 'NOT_STARTED' COMMENT 'progress status'," + "submitted bit(1) DEFAULT b'0' COMMENT 'submitted'," + "submit_time datetime NULL COMMENT 'submit time'," + "create_time datetime NULL COMMENT 'create time'," + "update_time datetime NULL COMMENT 'update time'," + "PRIMARY KEY (id)," + "UNIQUE KEY uk_student_training_progress (student_user_id, teaching_class_id, task_id)," + "KEY idx_student_training_progress_task (task_id)," + "KEY idx_student_training_progress_class (teaching_class_id)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='student training progress'"); jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS student_training_step_answer (" + "id varchar(64) NOT NULL COMMENT 'primary id'," + "progress_id varchar(64) NOT NULL COMMENT 'progress id'," + "student_user_id varchar(64) NOT NULL COMMENT 'student user id'," + "teaching_class_id varchar(64) NOT NULL COMMENT 'teaching class id'," + "task_id varchar(64) NOT NULL COMMENT 'training task id'," + "task_key varchar(128) NOT NULL COMMENT 'training task key'," + "step_no int NOT NULL COMMENT 'step number'," + "step_name varchar(128) NULL COMMENT 'step name'," + "answer_json longtext NULL COMMENT 'step answer json'," + "create_time datetime NULL COMMENT 'create time'," + "update_time datetime NULL COMMENT 'update time'," + "PRIMARY KEY (id)," + "UNIQUE KEY uk_student_training_step_answer (student_user_id, teaching_class_id, task_id, step_no)," + "KEY idx_student_training_step_answer_progress (progress_id)," + "KEY idx_student_training_step_answer_task (task_id)," + "KEY idx_student_training_step_answer_class (teaching_class_id)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='student training step answer'"); jdbcTemplate.execute("ALTER TABLE student_training_answer CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); jdbcTemplate.execute("ALTER TABLE student_training_progress CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); jdbcTemplate.execute("ALTER TABLE student_training_step_answer CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); backfillDetailTables(); studentTrainingAnswerTableChecked = true; } private void syncDetailTables(StudentTrainingAnswer answer) { if (jdbcTemplate == null || answer == null || StringUtils.isBlank(answer.getId())) { return; } String progressId = "p-" + answer.getId(); jdbcTemplate.update("insert into student_training_progress " + "(id, student_user_id, teaching_class_id, task_id, task_key, task_name, current_step, progress_status, submitted, submit_time, create_time, update_time) " + "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + "on duplicate key update task_key = values(task_key), task_name = values(task_name), current_step = values(current_step), " + "progress_status = values(progress_status), submitted = values(submitted), submit_time = values(submit_time), update_time = values(update_time)", progressId, answer.getStudentUserId(), answer.getTeachingClassId(), answer.getTaskId(), answer.getTaskKey(), answer.getTaskName(), answer.getCurrentStep(), answer.getProgressStatus(), Boolean.TRUE.equals(answer.getSubmitted()), answer.getSubmitTime(), answer.getCreateTime(), answer.getUpdateTime()); syncStepAnswer(answer, progressId, 1, answer.getStep1Answer()); syncStepAnswer(answer, progressId, 2, answer.getStep2Answer()); syncStepAnswer(answer, progressId, 3, answer.getStep3Answer()); syncStepAnswer(answer, progressId, 4, answer.getStep4Answer()); } private void syncStepAnswer(StudentTrainingAnswer answer, String progressId, int stepNo, String answerJson) { if (answerJson == null) { return; } jdbcTemplate.update("insert into student_training_step_answer " + "(id, progress_id, student_user_id, teaching_class_id, task_id, task_key, step_no, step_name, answer_json, create_time, update_time) " + "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + "on duplicate key update progress_id = values(progress_id), task_key = values(task_key), step_name = values(step_name), " + "answer_json = values(answer_json), update_time = values(update_time)", "s" + stepNo + "-" + answer.getId(), progressId, answer.getStudentUserId(), answer.getTeachingClassId(), answer.getTaskId(), answer.getTaskKey(), stepNo, "Step " + stepNo, answerJson, answer.getCreateTime(), answer.getUpdateTime()); } private void backfillDetailTables() { jdbcTemplate.execute("insert into student_training_progress " + "(id, student_user_id, teaching_class_id, task_id, task_key, task_name, current_step, progress_status, submitted, submit_time, create_time, update_time) " + "select concat('p-', id), student_user_id, teaching_class_id, task_id, task_key, task_name, current_step, progress_status, submitted, submit_time, create_time, update_time " + "from student_training_answer " + "on duplicate key update task_key = values(task_key), task_name = values(task_name), current_step = values(current_step), " + "progress_status = values(progress_status), submitted = values(submitted), submit_time = values(submit_time), update_time = values(update_time)"); backfillStep(1, "step1_answer"); backfillStep(2, "step2_answer"); backfillStep(3, "step3_answer"); backfillStep(4, "step4_answer"); } private void backfillStep(int stepNo, String answerColumn) { jdbcTemplate.execute("insert into student_training_step_answer " + "(id, progress_id, student_user_id, teaching_class_id, task_id, task_key, step_no, step_name, answer_json, create_time, update_time) " + "select concat('s" + stepNo + "-', id), concat('p-', id), student_user_id, teaching_class_id, task_id, task_key, " + stepNo + ", 'Step " + stepNo + "', " + answerColumn + ", create_time, update_time " + "from student_training_answer where " + answerColumn + " is not null " + "on duplicate key update progress_id = values(progress_id), task_key = values(task_key), step_name = values(step_name), " + "answer_json = values(answer_json), update_time = values(update_time)"); } }