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