7.5 KiB
7.5 KiB
03 · 技术选型与架构
所属:运动会积分榜系统 · 开发文档集 版本:v0.1(讨论稿) · 2026-09-09 上游:README · 相关:04-数据模型、10-部署与现场方案
1. 技术选型
1.1 候选方案对比
| 维度 | A. Node + TS + Fastify + Socket.IO | B. Go 单二进制 | C. Python + FastAPI |
|---|---|---|---|
| 实时推送 | Socket.IO 成熟(房间/重连/降级) | 需自己写重连与降级 | WebSocket 原生,重连自己写 |
| 前端生态 | 最强(Vue / Element Plus / 动效库) | 前端仍要 Node 构建 | 同左 |
| 现场部署 | 需装 Node 运行时 | 单文件,最优 | 需装 Python + 依赖 |
| 原生依赖风险 | better-sqlite3 需预编译二进制 | 纯 Go SQLite,无 cgo | 无 |
| 开发效率 | 高 | 中 | 中 |
| 单赛事规模(≤300 人、≤50 小项) | 绰绰有余 | 绰绰有余 | 绰绰有余 |
1.2 ✅ 建议结论
主方案 A:Node.js 22 + TypeScript + Fastify + Socket.IO + SQLite + Vue 3 + Vite + Pinia。
理由:
- 本项目 90% 的复杂度在两个前端界面 + 实时同步,Node 生态在这块最省事。
- 数据规模极小(几百行成绩),SQLite 同步读写完全够用,
better-sqlite3性能过剩。 - 单一语言贯穿前后端,类型可共享(前后端共用
shared/types),减少字段对不上的低级错误。 - 部署难点(运行时)可用「绿色部署包」化解:Node 运行时 + 构建产物 + 启动脚本打包,现场解压双击即可,见 10-部署与现场方案。
降级条件:若现场主机无法运行 Node(极端受限环境),切方案 B。切换成本主要在服务端,前端可完全复用,因此架构上把「领域逻辑」与「运行时」隔离,便于将来移植。
1.3 关键依赖(初稿,编码时锁版本)
服务端
| 依赖 | 用途 | 备注 |
|---|---|---|
fastify |
HTTP 框架 | 比 Express 快、TS 友好、插件体系清晰 |
@fastify/static |
托管前端构建产物 | 单端口对外 |
@fastify/cookie + @fastify/csrf-protection |
会话与 CSRF | 若用 cookie 会话 |
socket.io |
实时推送 | 房间、自动重连 |
better-sqlite3 |
SQLite 驱动 | 同步 API,简单可靠;备选 node:sqlite |
zod |
入参校验 | 与前端共享 schema |
argon2 / bcrypt |
密码哈希 | 优先 argon2id |
exceljs |
Excel 导出 | 导出成绩册 |
pino |
日志 | Fastify 默认 |
vitest |
单元测试 | 与 Vite 同族 |
前端
| 依赖 | 用途 |
|---|---|
vue 3 + vite + typescript |
基础 |
pinia |
状态管理 |
vue-router |
管理端路由 |
element-plus |
管理端组件库(表格/表单/对话框) |
socket.io-client |
实时客户端 |
dayjs |
时间处理 |
@vueuse/core |
常用组合式工具 |
大屏端不用组件库,手写 CSS(设计令牌待视觉定稿,见 D13),保证视觉可控与渲染性能。
2. 系统架构
2.1 部署拓扑
┌──────────────────────────────┐
│ 现场主机(唯一服务端) │
│ │
大屏机 A ──HTTP──▶│ Node 进程 │
(Chrome kiosk) │ ├─ HTTP API (/api) │
│ ├─ 静态资源 (/display /admin)│
大屏机 B ──HTTP──▶│ ├─ Socket.IO (/socket.io) │
│ └─ SQLite (data/scoreboard.db)│
平板/笔记本 ─HTTP─▶│ │
(管理面板) └──────────────────────────────┘
│
└── data/backups/ 自动备份
- 单进程、单端口,不引入 Nginx / Redis / 消息队列——现场少一个组件就少一个故障点。
- 大屏机与管理端都是浏览器,不需要额外安装客户端。
2.2 服务端模块划分
server/src/
├── main.ts 启动、装配、优雅退出
├── config.ts 配置(端口/路径/赛事默认值),支持环境变量覆盖
├── db/
│ ├── client.ts 连接、PRAGMA(WAL/foreign_keys/busy_timeout)
│ ├── migrations/ 版本化迁移(001_init.sql …)
│ └── seed.ts 首次运行初始化(默认管理员、默认积分模板)
├── domain/ ★纯逻辑,无 IO,最需要测试
│ ├── scoring/ 名次→积分、并列处理、权重
│ ├── standings/ 总分聚合、排序、tie-break、奖牌计数
│ ├── records/ 纪录比较与破纪录判定
│ └── ledger/ 积分流水的追加/作废/重算
├── services/ 事务编排(domain + db)
├── routes/ HTTP 接口层(薄)
├── realtime/ Socket.IO:房间、快照、增量、心跳
├── middleware/ 认证、权限、审计、错误处理、限流
└── util/
原则:domain/ 不 import 任何 fastify / socket.io / sqlite,输入输出都是普通对象。这样积分规则能被彻底单测,也方便将来移植到 Go。
2.3 数据流(一次成绩发布)
HTTP POST /api/results/publish
→ 校验权限 + 校验入参(zod)
→ 开启事务
├─ 写入/更新 results
├─ 作废旧积分流水,追加新流水
├─ 重算受影响单位的总分与名次
├─ 写 change_log(version++)
└─ 写 audit_log
→ 事务提交
→ realtime.broadcast(version, changes)
├─ room "display" → 大屏
└─ room "admin" → 其他管理端(多人协作时保持同步)
3. 目录结构
Scoreboard/
├── devdocs/ # 本文档集
├── server/ # Node + TS 服务端
│ ├── src/{db,domain,services,routes,realtime,middleware,util}
│ ├── tests/
│ └── package.json
├── web/ # Vue 3 + Vite
│ ├── src/
│ │ ├── shared/ # 类型、API 客户端、Socket 客户端、设计令牌
│ │ ├── display/ # 大屏入口
│ │ └── admin/ # 管理端入口
│ ├── display.html
│ ├── admin.html
│ └── index.html # 导航页
├── deploy/ # systemd / 启动脚本 / 打包脚本
├── data/ # 运行时数据(gitignore)
├── package.json # pnpm workspace 根
├── pnpm-workspace.yaml
└── README.md
4. 编码规范
| 项 | 约定 |
|---|---|
| TypeScript | strict: true;前后端共享类型放 web/src/shared 或独立 shared/ 包 |
| 命名 | 数据库表/字段 snake_case,TS 变量 camelCase,组件 PascalCase |
| 提交信息 | Conventional Commits(feat: / fix: / docs: / test: / chore:) |
| 分支 | main 保护,功能分支 feat/xxx |
| 代码风格 | Prettier + ESLint,提交前 lint-staged 校验 |
| 分层红线 | domain/ 禁止引入 IO 依赖(见 2.2) |
| 注释 | 只在解释「为什么」时写;不写复述代码的注释 |