'use strict'; /** * 024 - 综合大题作答表 big_question_answers * * 背景(2026-09-21 需求变更) * 中职组 / 高职组取消 Word 上传题,改为随机抽取一道大题、在页面内直接作答三小问。 * * 设计要点 * 1. **一考生一行**:UNIQUE(aggregation_uid) —— 满足「一个考生一行数据保存」的目标, * 且与题量配置解耦(客观题题量由 competition_times.choice_count/judge_count 配置, * 属逐题表 zhongzhi/gaozhi_submissions,与本表互不干扰)。 * 2. **题干快照 question_snapshot**:抽题那一刻把题干(标题/描述/三小问/分值)序列化落库, * 题目后续被管理员修改也不影响存证 → 防考生纠纷。 * 3. **不存储每题一列**:客观题 50+20=70 题且题量可配,逐题建列会导致配置每变更一次 * 就要 ALTER TABLE(反扩展性);大题固定 3 小问,故 answer_1/2/3 列式存储是安全的。 * * 隔离原则 * 新增隔离表,zhongzhi_submissions / gaozhi_submissions 零改动、零迁移。 * * 环境说明 * MariaDB 5.5.68:无 JSON 列类型(快照用 MEDIUMTEXT 存 JSON 文本); * DATETIME 不支持 DEFAULT CURRENT_TIMESTAMP,时间戳交给应用层写入。 */ module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.createTable('big_question_answers', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false, comment: '主键ID', }, aggregation_uid: { type: Sequelize.INTEGER, allowNull: false, comment: '聚合报名ID(中职/高职组 == contestants.id),唯一 → 一考生一行', }, contestant_id: { type: Sequelize.INTEGER, allowNull: true, comment: '选手ID(冗余,便于管理端查证与排障)', }, group: { type: Sequelize.STRING(20), allowNull: false, comment: '组别:zhongzhi/gaozhi', }, big_question_id: { type: Sequelize.INTEGER, allowNull: true, comment: '抽中的大题ID', }, big_question_uid: { type: Sequelize.STRING(64), allowNull: true, comment: '抽中的大题稳定标识', }, question_snapshot: { type: Sequelize.TEXT('medium'), allowNull: true, comment: '抽题时刻的题干快照 JSON(防纠纷存证)', }, answer_1: { type: Sequelize.TEXT, allowNull: true, comment: '第1小问作答(上限 2000 字,后端同步校验)', }, answer_2: { type: Sequelize.TEXT, allowNull: true, comment: '第2小问作答(上限 2000 字)', }, answer_3: { type: Sequelize.TEXT, allowNull: true, comment: '第3小问作答(上限 2000 字)', }, score_1: { type: Sequelize.INTEGER, allowNull: true, comment: '第1小问得分(0-10,人工阅卷)', }, score_2: { type: Sequelize.INTEGER, allowNull: true, comment: '第2小问得分(0-10,人工阅卷)', }, score_3: { type: Sequelize.INTEGER, allowNull: true, comment: '第3小问得分(0-10,人工阅卷)', }, total_score: { type: Sequelize.INTEGER, allowNull: true, comment: '三小问得分合计(阅卷后回写 contestants.word_score 取数依据)', }, submit_time: { type: Sequelize.DATE, allowNull: true, comment: '随整卷正式提交写入(占位/未提交为 NULL)', }, created_at: { type: Sequelize.DATE, allowNull: true, comment: '创建时间', }, updated_at: { type: Sequelize.DATE, allowNull: true, comment: '更新时间', }, }).then(() => { return Promise.all([ queryInterface.addIndex('big_question_answers', ['aggregation_uid'], { name: 'uk_big_answer_agg', unique: true }), queryInterface.addIndex('big_question_answers', ['group'], { name: 'idx_big_answer_group' }), queryInterface.addIndex('big_question_answers', ['big_question_uid'], { name: 'idx_big_answer_q' }), ]); }); }, down: async (queryInterface) => { await queryInterface.dropTable('big_question_answers'); }, };