249 lines
8.2 KiB
Markdown
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` 表。
|
|
- 首次启动:建库 → 跑迁移 → 写入种子数据(默认管理员 + 默认积分模板 + 默认赛事配置)。
|
|
- 默认管理员密码随机生成,打印到控制台,首次登录强制修改。
|