feat: 实训任务配置新增参考答案字段(reference_answer_name/url)

- TrainingTask/TrainingTaskClassConfig 实体与两个 mapper 透传新字段
- TrainingTaskServiceImpl:平台保存未传字段保留原值、默认任务修改同步班级、
  建表语句与 ensureReferenceAnswerColumns 存量自动补列
- 附手工迁移脚本 docs/sql/2026-10-15-training-task-reference-answer.sql
main
chenyuan 11 hours ago
parent e2ee0b1d42
commit 516aef2156

@ -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;

@ -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;
}

@ -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;
}

@ -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<String> 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) {

@ -21,6 +21,8 @@
<result column="guide_manual_url" jdbcType="VARCHAR" property="guideManualUrl" />
<result column="ai_manual_name" jdbcType="VARCHAR" property="aiManualName" />
<result column="ai_manual_url" jdbcType="VARCHAR" property="aiManualUrl" />
<result column="reference_answer_name" jdbcType="VARCHAR" property="referenceAnswerName" />
<result column="reference_answer_url" jdbcType="VARCHAR" property="referenceAnswerUrl" />
<result column="sort" jdbcType="INTEGER" property="sort" />
<result column="enabled" jdbcType="BIT" property="enabled" />
<result column="created_by" jdbcType="VARCHAR" property="createdBy" />
@ -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
</sql>
@ -98,6 +101,8 @@
<if test="guideManualUrl != null">guide_manual_url,</if>
<if test="aiManualName != null">ai_manual_name,</if>
<if test="aiManualUrl != null">ai_manual_url,</if>
<if test="referenceAnswerName != null">reference_answer_name,</if>
<if test="referenceAnswerUrl != null">reference_answer_url,</if>
<if test="sort != null">sort,</if>
<if test="enabled != null">enabled,</if>
<if test="createdBy != null">created_by,</if>
@ -124,6 +129,8 @@
<if test="guideManualUrl != null">#{guideManualUrl,jdbcType=VARCHAR},</if>
<if test="aiManualName != null">#{aiManualName,jdbcType=VARCHAR},</if>
<if test="aiManualUrl != null">#{aiManualUrl,jdbcType=VARCHAR},</if>
<if test="referenceAnswerName != null">#{referenceAnswerName,jdbcType=VARCHAR},</if>
<if test="referenceAnswerUrl != null">#{referenceAnswerUrl,jdbcType=VARCHAR},</if>
<if test="sort != null">#{sort,jdbcType=INTEGER},</if>
<if test="enabled != null">#{enabled,jdbcType=BIT},</if>
<if test="createdBy != null">#{createdBy,jdbcType=VARCHAR},</if>
@ -151,6 +158,8 @@
<if test="guideManualUrl != null">guide_manual_url = #{guideManualUrl,jdbcType=VARCHAR},</if>
<if test="aiManualName != null">ai_manual_name = #{aiManualName,jdbcType=VARCHAR},</if>
<if test="aiManualUrl != null">ai_manual_url = #{aiManualUrl,jdbcType=VARCHAR},</if>
<if test="referenceAnswerName != null">reference_answer_name = #{referenceAnswerName,jdbcType=VARCHAR},</if>
<if test="referenceAnswerUrl != null">reference_answer_url = #{referenceAnswerUrl,jdbcType=VARCHAR},</if>
<if test="sort != null">sort = #{sort,jdbcType=INTEGER},</if>
<if test="enabled != null">enabled = #{enabled,jdbcType=BIT},</if>
<if test="updateTime != null">update_time = #{updateTime,jdbcType=TIMESTAMP},</if>

@ -20,6 +20,8 @@
<result column="guide_manual_url" jdbcType="VARCHAR" property="guideManualUrl" />
<result column="ai_manual_name" jdbcType="VARCHAR" property="aiManualName" />
<result column="ai_manual_url" jdbcType="VARCHAR" property="aiManualUrl" />
<result column="reference_answer_name" jdbcType="VARCHAR" property="referenceAnswerName" />
<result column="reference_answer_url" jdbcType="VARCHAR" property="referenceAnswerUrl" />
<result column="sort" jdbcType="INTEGER" property="sort" />
<result column="enabled" jdbcType="BIT" property="enabled" />
<result column="created_by" jdbcType="VARCHAR" property="createdBy" />
@ -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
</sql>
@ -93,6 +96,8 @@
<if test="guideManualUrl != null">guide_manual_url,</if>
<if test="aiManualName != null">ai_manual_name,</if>
<if test="aiManualUrl != null">ai_manual_url,</if>
<if test="referenceAnswerName != null">reference_answer_name,</if>
<if test="referenceAnswerUrl != null">reference_answer_url,</if>
<if test="sort != null">sort,</if>
<if test="enabled != null">enabled,</if>
<if test="createdBy != null">created_by,</if>
@ -118,6 +123,8 @@
<if test="guideManualUrl != null">#{guideManualUrl,jdbcType=VARCHAR},</if>
<if test="aiManualName != null">#{aiManualName,jdbcType=VARCHAR},</if>
<if test="aiManualUrl != null">#{aiManualUrl,jdbcType=VARCHAR},</if>
<if test="referenceAnswerName != null">#{referenceAnswerName,jdbcType=VARCHAR},</if>
<if test="referenceAnswerUrl != null">#{referenceAnswerUrl,jdbcType=VARCHAR},</if>
<if test="sort != null">#{sort,jdbcType=INTEGER},</if>
<if test="enabled != null">#{enabled,jdbcType=BIT},</if>
<if test="createdBy != null">#{createdBy,jdbcType=VARCHAR},</if>
@ -146,6 +153,8 @@
<if test="guideManualUrl != null">guide_manual_url = #{guideManualUrl,jdbcType=VARCHAR},</if>
<if test="aiManualName != null">ai_manual_name = #{aiManualName,jdbcType=VARCHAR},</if>
<if test="aiManualUrl != null">ai_manual_url = #{aiManualUrl,jdbcType=VARCHAR},</if>
<if test="referenceAnswerName != null">reference_answer_name = #{referenceAnswerName,jdbcType=VARCHAR},</if>
<if test="referenceAnswerUrl != null">reference_answer_url = #{referenceAnswerUrl,jdbcType=VARCHAR},</if>
<if test="sort != null">sort = #{sort,jdbcType=INTEGER},</if>
<if test="enabled != null">enabled = #{enabled,jdbcType=BIT},</if>
<if test="createdBy != null">created_by = #{createdBy,jdbcType=VARCHAR},</if>

Loading…
Cancel
Save