8.2 KiB
8.2 KiB
04 · 数据模型
所属:运动会积分榜系统 · 开发文档集 版本:v0.1(讨论稿) · 2026-09-09 上游:README · 相关:02-积分与排名规则、05-接口与实时协议
1. 实体关系概览
settings(赛事配置)
teams(单位) ──┬── athletes(运动员) ──┐
│ ├── entries(报名项) ── results(成绩)
sports(大项) ── events(小项) ────────┘ │
│ ├── record_breaks(破纪录)
└── scoring_profiles(积分模板)┘
│
score_ledger(积分流水) ── 汇总 ──▶ standings(排名视图/缓存)
point_adjustments(人工调整) ──────────┘
users / sessions / audit_logs / change_log
2. 表结构
SQLite 是动态类型,字段类型按习惯书写,真正的约束靠
CHECK、UNIQUE、外键与索引保证。
settings — 赛事配置
| 字段 | 类型 | 说明 |
|---|---|---|
| key | TEXT PK | 如 event.name、display.rotate_seconds |
| value | TEXT | JSON 或纯文本 |
| updated_at | TEXT | ISO8601 |
关键配置项:赛事名称、副标题、主办方、Logo 路径、主题色、大屏轮播间隔、排名模式、默认积分模板、是否启用审核流。
teams — 参赛单位
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INTEGER PK | AUTOINCREMENT | |
| code | TEXT | UNIQUE NOT NULL | 短代码,用于排序与导出 |
| name | TEXT | NOT NULL | 全称 |
| short_name | TEXT | 大屏显示用 | |
| logo_path | TEXT | 相对 data/uploads/ |
|
| color | TEXT | 主题色,用于图表 | |
| sort_order | INTEGER | DEFAULT 0 | 自定义排序 |
| active | INTEGER | DEFAULT 1 | 软删除 |
athletes — 运动员
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | INTEGER PK | ||
| name | TEXT | NOT NULL | |
| gender | TEXT | CHECK in ('男','女') | |
| team_id | INTEGER | FK teams | |
| employee_no | TEXT | 工号/学号,可空 | |
| active | INTEGER | DEFAULT 1 |
sports — 大项
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| code / name / short_name | TEXT | |
| scoring_profile_id | INTEGER FK | 该大项默认积分模板 |
| sort_order / active | INTEGER |
events — 小项
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| sport_id | INTEGER FK | |
| code | TEXT UNIQUE | |
| name | TEXT | 如「男子100米」 |
| gender | TEXT | 男/女/混合/不限 |
| event_type | TEXT | individual / team / relay / match(球类) |
| scoring_profile_id | INTEGER FK NULL | 覆盖大项模板 |
| multiplier | REAL DEFAULT 1 | 权重,双倍项目填 2 |
| record_eligible | INTEGER DEFAULT 1 | 是否参与纪录榜 |
| status | TEXT | pending / ongoing / finished / published |
| scheduled_at | TEXT | 计划时间 |
| venue | TEXT | 场地 |
| sort_order / remark |
scoring_profiles — 积分模板
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| code | TEXT UNIQUE | |
| name | TEXT | 如「田径前8名」 |
| tie_mode | TEXT | skip / average / higher,默认 skip |
| record_bonus_mode | TEXT | max / sum,默认 max |
scoring_profile_rules — 模板明细
| 字段 | 类型 | 说明 |
|---|---|---|
| profile_id | INTEGER FK | 联合主键 |
| rank | INTEGER | 名次 |
| points | REAL | 分值 |
entries — 报名项
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| event_id | INTEGER FK | |
| team_id | INTEGER FK | |
| athlete_id | INTEGER FK NULL | 个人项目 |
| member_names | TEXT NULL | 团体/接力成员,JSON 数组 |
| bib | TEXT | 号码/道次 |
| seed | TEXT | 种子/预赛成绩 |
| status | TEXT | registered / scratched / dq / dns |
| remark | TEXT | |
| UNIQUE(event_id, team_id, athlete_id) | 防重复报名 |
results — 成绩
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| event_id | INTEGER FK | 冗余,便于查询 |
| entry_id | INTEGER FK UNIQUE | 一条报名项一条成绩 |
| rank | INTEGER NULL | 名次,可空 |
| result_text | TEXT | 展示用,如「1:06.33」「6.12米」 |
| result_value | REAL NULL | 比较用(秒/米/分),用于排序与破纪录判断 |
| unit | TEXT | s / m / pts / 次 |
| is_tie | INTEGER | 是否并列 |
| points_override | REAL NULL | 手工覆盖积分(需理由) |
| status | TEXT | ok / dq / dns / dnf / withdrawn |
| state | TEXT | draft / published ★ |
| recorded_by / recorded_at | ||
| verified_by / verified_at | ||
| note | TEXT |
records — 纪录库
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| event_id | INTEGER FK | |
| level | TEXT | meet / city / province / national |
| result_value | REAL | |
| result_text | TEXT | |
| holder_name / holder_team_id | ||
| achieved_at | TEXT | |
| is_current | INTEGER | 是否当前有效纪录 |
| source | TEXT | history(历届导入)/ current(本届产生) |
record_breaks — 破纪录登记
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| event_id / entry_id | ||
| level | TEXT | |
| result_value / result_text | ||
| bonus_points | REAL | |
| state | TEXT | pending / approved / rejected |
| approved_by / approved_at |
score_ledger — 积分流水(核心)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| team_id | INTEGER FK | 归属单位 |
| athlete_id | INTEGER NULL | 归属个人(个人榜用) |
| event_id / entry_id / result_id | NULL | 溯源 |
| source_type | TEXT | result / record_bonus / adjustment |
| source_id | INTEGER | 对应来源记录 id |
| points | REAL | 可正可负 |
| voided | INTEGER DEFAULT 0 | 作废标记 |
| voided_by / voided_at / void_reason | ||
| created_by / created_at |
point_adjustments — 人工调整
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK | |
| team_id | INTEGER FK | |
| delta | REAL | 正负均可 |
| category | TEXT | penalty / bonus / correction |
| reason | TEXT NOT NULL | 必填 |
| event_id | INTEGER NULL | 可选关联 |
| voided / created_by / created_at |
users / sessions / audit_logs / change_log
| 表 | 关键字段 |
|---|---|
| users | id, username UNIQUE, password_hash, display_name, role, active, last_login_at |
| sessions | id, user_id, token_hash, expires_at, ip, user_agent |
| audit_logs | id, user_id, action, entity_type, entity_id, before_json, after_json, ip, request_id, created_at |
| change_log | id(version), entity_type, entity_id, action, payload_json, actor_id, created_at |
3. 排名如何算(性能与一致性)
数据量:单位 ≤50、小项 ≤100、报名项 ≤2000。每次变更后全量重算耗时可忽略(毫秒级),因此:
- 不引入复杂增量算法,直接
SELECT ... FROM score_ledger WHERE voided=0 GROUP BY team_id聚合。 - 排名结果可物化到
standings_cache供大屏快速读取;缓存失效时以流水为准重算。 - 一致性策略:流水是唯一真相,缓存永远可重建。任何「总分不对」的问题,重算一遍即可自愈。
4. 索引与约束
索引
| 索引 | 用途 |
|---|---|
entries(event_id) |
按项目取报名项 |
entries(team_id) |
按单位取报名项 |
results(event_id, state) |
大屏取已发布成绩 |
score_ledger(team_id, voided) |
总分聚合 |
score_ledger(source_type, source_id) |
按来源作废流水 |
change_log(id) |
增量补发 |
约束
results.entry_id UNIQUEscoring_profile_rules(profile_id, rank)联合主键PRAGMA foreign_keys=ON
删除策略
- 业务数据一律软删除 / 作废,不做物理删除。
5. 迁移与初始化
- 迁移文件
db/migrations/NNN_描述.sql,按序号执行,记录在schema_migrations表。 - 首次启动:建库 → 跑迁移 → 写入种子数据(默认管理员 + 默认积分模板 + 默认赛事配置)。
- 默认管理员密码随机生成,打印到控制台,首次登录强制修改。