Files
competition-website/app/model/contestant.js
WorkBuddy a303cf2d53 chore: 后端首次基线提交
运行必须源码:app/(controller/model/service/middleware/lib/utils/validate/router)、config/、database/(migrations/seeders/sql)、启动脚本、app/public/templates 模板。
已忽略运行非必须:node_modules/、logs/、run/、app/public 运行时上传数据、.env*、构建/缓存产物。
2026-08-17 12:25:42 +08:00

161 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
/**
* 选手模型 - Contestant
*
* 存储参赛选手的基本信息,包括身份认证、组别、成绩等。
* 选手通过身份证号和密码进行登录认证。
*/
module.exports = app => {
const { STRING, INTEGER, BOOLEAN, DATE } = app.Sequelize;
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: {
type: INTEGER,
allowNull: true,
defaultValue: null,
comment: '选手总分(exam_score + word_score)',
},
// 卷面分(选择题+判断题自动计算)
exam_score: {
type: INTEGER,
allowNull: true,
defaultValue: null,
comment: '卷面考试成绩',
},
// Word题分数(管理员手动打分)
word_score: {
type: INTEGER,
allowNull: true,
defaultValue: null,
comment: 'Word题得分',
},
// PPT分数(本科/教师组管理员手动打分)
ppt_score: {
type: INTEGER,
allowNull: true,
defaultValue: null,
comment: 'PPT得分(本科/教师组)',
},
// 所属团队 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;
};