diff --git a/docs/sql/2026-10-15-training-task-reference-answer.sql b/docs/sql/2026-10-15-training-task-reference-answer.sql new file mode 100644 index 0000000..bf83dbb --- /dev/null +++ b/docs/sql/2026-10-15-training-task-reference-answer.sql @@ -0,0 +1,10 @@ +-- 实训任务新增“参考答案”资料位(教师端/平台管理端实训任务管理上传)。 +-- 后端 TrainingTaskServiceImpl 启动时会自动补列(ensureReferenceAnswerColumns), +-- 本脚本供手工执行或核对用,重复执行前请先确认列不存在。 +ALTER TABLE training_task + ADD COLUMN reference_answer_name varchar(255) NULL AFTER ai_manual_url, + ADD COLUMN reference_answer_url varchar(500) NULL AFTER reference_answer_name; + +ALTER TABLE training_task_class_config + ADD COLUMN reference_answer_name varchar(255) NULL AFTER ai_manual_url, + ADD COLUMN reference_answer_url varchar(500) NULL AFTER reference_answer_name; diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTask.java b/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTask.java index 5d299a0..93701e0 100644 --- a/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTask.java +++ b/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTask.java @@ -59,6 +59,12 @@ public class TrainingTask { @ApiModelProperty("AI排错指导手册地址") private String aiManualUrl; + @ApiModelProperty("参考答案名称") + private String referenceAnswerName; + + @ApiModelProperty("参考答案地址") + private String referenceAnswerUrl; + @ApiModelProperty("排序") private Integer sort; @@ -218,6 +224,22 @@ public class TrainingTask { this.aiManualUrl = aiManualUrl == null ? null : aiManualUrl.trim(); } + public String getReferenceAnswerName() { + return referenceAnswerName; + } + + public void setReferenceAnswerName(String referenceAnswerName) { + this.referenceAnswerName = referenceAnswerName == null ? null : referenceAnswerName.trim(); + } + + public String getReferenceAnswerUrl() { + return referenceAnswerUrl; + } + + public void setReferenceAnswerUrl(String referenceAnswerUrl) { + this.referenceAnswerUrl = referenceAnswerUrl == null ? null : referenceAnswerUrl.trim(); + } + public Integer getSort() { return sort; } diff --git a/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTaskClassConfig.java b/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTaskClassConfig.java index df6331b..34aa33b 100644 --- a/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTaskClassConfig.java +++ b/src/main/java/com/sztzjy/linkCommerce/entity/TrainingTaskClassConfig.java @@ -62,6 +62,12 @@ public class TrainingTaskClassConfig { @ApiModelProperty("AI troubleshooting manual URL") private String aiManualUrl; + @ApiModelProperty("Reference answer name") + private String referenceAnswerName; + + @ApiModelProperty("Reference answer URL") + private String referenceAnswerUrl; + @ApiModelProperty("Sort order") private Integer sort; @@ -229,6 +235,22 @@ public class TrainingTaskClassConfig { this.aiManualUrl = aiManualUrl == null ? null : aiManualUrl.trim(); } + public String getReferenceAnswerName() { + return referenceAnswerName; + } + + public void setReferenceAnswerName(String referenceAnswerName) { + this.referenceAnswerName = referenceAnswerName == null ? null : referenceAnswerName.trim(); + } + + public String getReferenceAnswerUrl() { + return referenceAnswerUrl; + } + + public void setReferenceAnswerUrl(String referenceAnswerUrl) { + this.referenceAnswerUrl = referenceAnswerUrl == null ? null : referenceAnswerUrl.trim(); + } + public Integer getSort() { return sort; } diff --git a/src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java b/src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java index 166619a..1de4ad3 100644 --- a/src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java +++ b/src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java @@ -36,6 +36,7 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -142,6 +143,8 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { String incomingGuideManualUrl = task.getGuideManualUrl(); String incomingAiManualName = task.getAiManualName(); String incomingAiManualUrl = task.getAiManualUrl(); + String incomingReferenceAnswerName = task.getReferenceAnswerName(); + String incomingReferenceAnswerUrl = task.getReferenceAnswerUrl(); normalizeTask(task); if (incomingTrainingDataName == null) task.setTrainingDataName(existing.getTrainingDataName()); if (incomingTrainingDataUrl == null) task.setTrainingDataUrl(existing.getTrainingDataUrl()); @@ -149,6 +152,8 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { if (incomingGuideManualUrl == null) task.setGuideManualUrl(existing.getGuideManualUrl()); if (incomingAiManualName == null) task.setAiManualName(existing.getAiManualName()); if (incomingAiManualUrl == null) task.setAiManualUrl(existing.getAiManualUrl()); + if (incomingReferenceAnswerName == null) task.setReferenceAnswerName(existing.getReferenceAnswerName()); + if (incomingReferenceAnswerUrl == null) task.setReferenceAnswerUrl(existing.getReferenceAnswerUrl()); TrainingTask builtInTask = requireBuiltInTask(existing.getTaskKey()); task.setId(id); task.setTaskKey(existing.getTaskKey()); @@ -647,6 +652,14 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { update.setAiManualUrl(updatedTask.getAiManualUrl()); changed = true; } + if (shouldSynchronize(classTask.getReferenceAnswerName(), previousTask.getReferenceAnswerName(), updatedTask.getReferenceAnswerName())) { + update.setReferenceAnswerName(updatedTask.getReferenceAnswerName()); + changed = true; + } + if (shouldSynchronize(classTask.getReferenceAnswerUrl(), previousTask.getReferenceAnswerUrl(), updatedTask.getReferenceAnswerUrl())) { + update.setReferenceAnswerUrl(updatedTask.getReferenceAnswerUrl()); + changed = true; + } if (changed) { update.setUpdateTime(new Date()); trainingTaskClassConfigMapper.updateByPrimaryKeySelective(update); @@ -723,6 +736,8 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { task.setGuideManualUrl(config.getGuideManualUrl()); task.setAiManualName(config.getAiManualName()); task.setAiManualUrl(config.getAiManualUrl()); + task.setReferenceAnswerName(config.getReferenceAnswerName()); + task.setReferenceAnswerUrl(config.getReferenceAnswerUrl()); task.setSort(config.getSort()); task.setEnabled(config.getEnabled()); task.setCreatedBy(config.getCreatedBy()); @@ -751,6 +766,8 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { config.setGuideManualUrl(task.getGuideManualUrl()); config.setAiManualName(task.getAiManualName()); config.setAiManualUrl(task.getAiManualUrl()); + config.setReferenceAnswerName(task.getReferenceAnswerName()); + config.setReferenceAnswerUrl(task.getReferenceAnswerUrl()); config.setSort(task.getSort()); config.setEnabled(task.getEnabled()); config.setCreatedBy(operatorId); @@ -780,6 +797,8 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { "guide_manual_url varchar(500) NULL COMMENT '实训指导手册地址'," + "ai_manual_name varchar(255) NULL COMMENT 'AI排错指导手册名称'," + "ai_manual_url varchar(500) NULL COMMENT 'AI排错指导手册地址'," + + "reference_answer_name varchar(255) NULL COMMENT '参考答案名称'," + + "reference_answer_url varchar(500) NULL COMMENT '参考答案地址'," + "sort int DEFAULT 0 COMMENT '排序'," + "enabled bit(1) DEFAULT b'1' COMMENT '是否启用'," + "created_by varchar(64) NULL COMMENT '创建人'," + @@ -793,6 +812,7 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { jdbcTemplate.execute("ALTER TABLE training_task CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); ensureTextColumn("training_task", "step_one_questions", "第一步选择题JSON数组"); ensureMaterialColumns("training_task"); + ensureReferenceAnswerColumns("training_task"); trainingTaskTableChecked = true; } @@ -820,6 +840,8 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { "guide_manual_url varchar(500) NULL," + "ai_manual_name varchar(255) NULL," + "ai_manual_url varchar(500) NULL," + + "reference_answer_name varchar(255) NULL," + + "reference_answer_url varchar(500) NULL," + "sort int DEFAULT 0," + "enabled bit(1) DEFAULT b'1'," + "created_by varchar(64) NULL," + @@ -832,6 +854,7 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Class scoped training task config'"); ensureTextColumn("training_task_class_config", "step_one_questions", "第一步选择题JSON数组"); ensureMaterialColumns("training_task_class_config"); + ensureReferenceAnswerColumns("training_task_class_config"); trainingTaskClassConfigTableChecked = true; } @@ -872,6 +895,23 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { + " ADD COLUMN ai_manual_url varchar(500) NULL AFTER ai_manual_name"); } + /** 补齐参考答案列:已有四类资料列的部署需要追加参考答案两列。 */ + private void ensureReferenceAnswerColumns(String tableName) { + if (jdbcTemplate == null) { + return; + } + Integer count = jdbcTemplate.queryForObject( + "SELECT COUNT(1) FROM information_schema.COLUMNS " + + "WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'reference_answer_url'", + Integer.class, tableName); + if (count != null && count > 0) { + return; + } + jdbcTemplate.execute("ALTER TABLE " + tableName + + " ADD COLUMN reference_answer_name varchar(255) NULL AFTER ai_manual_url," + + " ADD COLUMN reference_answer_url varchar(500) NULL AFTER reference_answer_name"); + } + TrainingTask buildTask(TrainingTaskImportDTO row, int rowNumber, List errors) { String projectName = StringUtils.trimToEmpty(row.getProjectName()); String taskKey = StringUtils.trimToEmpty(row.getTaskKey()); @@ -952,11 +992,24 @@ public class TrainingTaskServiceImpl implements TrainingTaskService { task.setGuideManualUrl(StringUtils.trimToEmpty(task.getGuideManualUrl())); task.setAiManualName(StringUtils.trimToEmpty(task.getAiManualName())); task.setAiManualUrl(StringUtils.trimToEmpty(task.getAiManualUrl())); + task.setReferenceAnswerName(StringUtils.trimToEmpty(task.getReferenceAnswerName())); + task.setReferenceAnswerUrl(StringUtils.trimToEmpty(task.getReferenceAnswerUrl())); + validateReferenceAnswerFile(task.getReferenceAnswerName()); if (task.getEnabled() == null) { task.setEnabled(Boolean.TRUE); } } + private void validateReferenceAnswerFile(String fileName) { + if (StringUtils.isBlank(fileName)) { + return; + } + String normalized = fileName.trim().toLowerCase(Locale.ROOT); + if (!normalized.endsWith(".doc") && !normalized.endsWith(".docx")) { + throw new IllegalArgumentException("参考答案仅支持Word文件(.doc或.docx)"); + } + } + private TrainingTask requireBuiltInTask(String taskKey) { TrainingTask builtInTask = getBuiltInTask(taskKey); if (builtInTask == null) { diff --git a/src/main/resources/mappers/TrainingTaskClassConfigMapper.xml b/src/main/resources/mappers/TrainingTaskClassConfigMapper.xml index 92e7f58..1bd019d 100644 --- a/src/main/resources/mappers/TrainingTaskClassConfigMapper.xml +++ b/src/main/resources/mappers/TrainingTaskClassConfigMapper.xml @@ -21,6 +21,8 @@ + + @@ -32,6 +34,7 @@ id, teaching_class_id, task_key, project_name, task_name, knowledge, background, objectives, requirements, steps, step_one_questions, material_name, material_url, training_data_name, training_data_url, guide_manual_name, guide_manual_url, ai_manual_name, ai_manual_url, + reference_answer_name, reference_answer_url, sort, enabled, created_by, create_time, update_time @@ -98,6 +101,8 @@ guide_manual_url, ai_manual_name, ai_manual_url, + reference_answer_name, + reference_answer_url, sort, enabled, created_by, @@ -124,6 +129,8 @@ #{guideManualUrl,jdbcType=VARCHAR}, #{aiManualName,jdbcType=VARCHAR}, #{aiManualUrl,jdbcType=VARCHAR}, + #{referenceAnswerName,jdbcType=VARCHAR}, + #{referenceAnswerUrl,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{enabled,jdbcType=BIT}, #{createdBy,jdbcType=VARCHAR}, @@ -151,6 +158,8 @@ guide_manual_url = #{guideManualUrl,jdbcType=VARCHAR}, ai_manual_name = #{aiManualName,jdbcType=VARCHAR}, ai_manual_url = #{aiManualUrl,jdbcType=VARCHAR}, + reference_answer_name = #{referenceAnswerName,jdbcType=VARCHAR}, + reference_answer_url = #{referenceAnswerUrl,jdbcType=VARCHAR}, sort = #{sort,jdbcType=INTEGER}, enabled = #{enabled,jdbcType=BIT}, update_time = #{updateTime,jdbcType=TIMESTAMP}, diff --git a/src/main/resources/mappers/TrainingTaskMapper.xml b/src/main/resources/mappers/TrainingTaskMapper.xml index a2e4c63..3e3676f 100644 --- a/src/main/resources/mappers/TrainingTaskMapper.xml +++ b/src/main/resources/mappers/TrainingTaskMapper.xml @@ -20,6 +20,8 @@ + + @@ -31,6 +33,7 @@ id, task_key, project_name, task_name, knowledge, background, objectives, requirements, steps, step_one_questions, material_name, material_url, training_data_name, training_data_url, guide_manual_name, guide_manual_url, ai_manual_name, ai_manual_url, + reference_answer_name, reference_answer_url, sort, enabled, created_by, create_time, update_time @@ -93,6 +96,8 @@ guide_manual_url, ai_manual_name, ai_manual_url, + reference_answer_name, + reference_answer_url, sort, enabled, created_by, @@ -118,6 +123,8 @@ #{guideManualUrl,jdbcType=VARCHAR}, #{aiManualName,jdbcType=VARCHAR}, #{aiManualUrl,jdbcType=VARCHAR}, + #{referenceAnswerName,jdbcType=VARCHAR}, + #{referenceAnswerUrl,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{enabled,jdbcType=BIT}, #{createdBy,jdbcType=VARCHAR}, @@ -146,6 +153,8 @@ guide_manual_url = #{guideManualUrl,jdbcType=VARCHAR}, ai_manual_name = #{aiManualName,jdbcType=VARCHAR}, ai_manual_url = #{aiManualUrl,jdbcType=VARCHAR}, + reference_answer_name = #{referenceAnswerName,jdbcType=VARCHAR}, + reference_answer_url = #{referenceAnswerUrl,jdbcType=VARCHAR}, sort = #{sort,jdbcType=INTEGER}, enabled = #{enabled,jdbcType=BIT}, created_by = #{createdBy,jdbcType=VARCHAR},