Files
Scoreboard/devdocs/03-技术选型与架构.md
T

7.5 KiB
Raw Blame History

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。

理由:

  1. 本项目 90% 的复杂度在两个前端界面 + 实时同步,Node 生态在这块最省事。
  2. 数据规模极小(几百行成绩),SQLite 同步读写完全够用,better-sqlite3 性能过剩。
  3. 单一语言贯穿前后端,类型可共享(前后端共用 shared/types),减少字段对不上的低级错误。
  4. 部署难点(运行时)可用「绿色部署包」化解: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)
注释 只在解释「为什么」时写;不写复述代码的注释