Files
Scoreboard/devdocs/04-数据模型.md
T

249 lines
8.2 KiB
Markdown

# 04 · 数据模型
> 所属:运动会积分榜系统 · 开发文档集
> 版本:v0.1(讨论稿) · 2026-09-09
> 上游:[README](./README.md) · 相关:[02-积分与排名规则](./02-积分与排名规则.md)、[05-接口与实时协议](./05-接口与实时协议.md)
---
## 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 UNIQUE`
- `scoring_profile_rules(profile_id, rank)` 联合主键
- `PRAGMA foreign_keys=ON`
**删除策略**
- 业务数据一律**软删除 / 作废**,不做物理删除。
---
## 5. 迁移与初始化
- 迁移文件 `db/migrations/NNN_描述.sql`,按序号执行,记录在 `schema_migrations` 表。
- 首次启动:建库 → 跑迁移 → 写入种子数据(默认管理员 + 默认积分模板 + 默认赛事配置)。
- 默认管理员密码随机生成,打印到控制台,首次登录强制修改。