'use strict'; /** * 选手模型 - Contestant * * 存储参赛选手的基本信息,包括身份认证、组别、成绩等。 * 选手通过身份证号和密码进行登录认证。 */ module.exports = app => { const { STRING, INTEGER, BOOLEAN, DATE } = app.Sequelize; // 分数列统一走共享工厂(DECIMAL(6,2) + Number 兜底,见 app/lib/decimal_score.js) const decimalScore = require('../lib/decimal_score')(app); const Contestant = app.model.define('contestant', { // 主键 ID,自增 id: { type: INTEGER, primaryKey: true, autoIncrement: true, comment: '选手唯一标识', }, // 身份证号,唯一,长度 18,用于选手登录 id_number: { type: STRING(18), allowNull: false, unique: true, comment: '身份证号(登录账号)', validate: { len: [18, 18], // 严格校验 18 位身份证号 }, }, // 选手姓名 name: { type: STRING(50), allowNull: false, comment: '选手姓名', }, // 手机号码 phone: { type: STRING(20), allowNull: false, comment: '手机号码', }, // 参赛组别:zhongzhi(中职) / gaozhi(高职) / benke(本科) / jiaoshi(教师) group: { type: STRING(20), allowNull: false, comment: '参赛组别', validate: { isIn: [['zhongzhi', 'gaozhi', 'benke', 'jiaoshi']], }, }, // 登录密码(存储明文,生产环境应使用 bcrypt 加密存储) password: { type: STRING(100), allowNull: false, defaultValue: '', comment: '登录密码', }, // 账号是否已激活(激活后才可以登录) activated: { type: BOOLEAN, allowNull: false, defaultValue: false, comment: '账号激活状态', }, // 总分(管理员打分后更新,NULL 表示尚未评分) score: decimalScore('score', '选手总分(exam_score + word_score)'), // 卷面分(选择题+判断题自动计算) exam_score: decimalScore('exam_score', '卷面考试成绩'), // 综合大题 / Word 题分数(管理员手动打分,可含 2 位小数) word_score: decimalScore('word_score', 'Word题/综合大题得分(可含 2 位小数)'), // PPT分数(本科/教师组管理员手动打分) ppt_score: decimalScore('ppt_score', 'PPT得分(本科/教师组,可含 2 位小数)'), // 所属团队 ID(外键关联 teams 表) team_id: { type: INTEGER, allowNull: true, defaultValue: null, comment: '所属团队ID', }, // 测试数据标记:0=生产数据,1=测试数据(重置时仅重置测试数据) test_flag: { type: INTEGER, allowNull: false, defaultValue: 0, comment: '测试数据标记:0=生产数据,1=测试数据', }, // 记录创建时间 created_at: { type: DATE, allowNull: false, defaultValue: app.Sequelize.literal('CURRENT_TIMESTAMP'), comment: '创建时间', }, // 记录更新时间 updated_at: { type: DATE, allowNull: false, defaultValue: app.Sequelize.literal('CURRENT_TIMESTAMP'), comment: '更新时间', }, }, { // 表名保持为 contestants(原样,不做复数变换) tableName: 'contestants', // 不自动生成 createdAt / updatedAt(我们手动定义 created_at / updated_at) timestamps: false, // 下划线命名 underscored: true, // 索引 indexes: [ { unique: true, fields: ['id_number'], }, { fields: ['group'], }, { fields: ['team_id'], }, ], }); Contestant.associate = () => { app.model.Contestant.belongsTo(app.model.Team, { foreignKey: 'team_id', targetKey: 'team_uid', as: 'team', }); app.model.Contestant.hasMany(app.model.ExamSubmission, { foreignKey: 'contestant_id', as: 'examSubmissions', }); app.model.Contestant.hasMany(app.model.WorkSubmission, { foreignKey: 'contestant_id', as: 'workSubmissions', }); }; return Contestant; };