You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

379 lines
20 KiB
Java

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);
requireNotDemo(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);
requireNotDemo(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);
requireNotDemo(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 void requireNotDemo(JwtUser user) {
if (user != null && user.isDemoMode()) {
throw new ServiceException(HttpStatus.FORBIDDEN, "演示模式不保存数据");
}
}
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)");
}
}