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

170 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 03 · 技术选型与架构
> 所属:运动会积分榜系统 · 开发文档集
> 版本:v0.1(讨论稿) · 2026-09-09
> 上游:[README](./README.md) · 相关:[04-数据模型](./04-数据模型.md)、[10-部署与现场方案](./10-部署与现场方案.md)
---
## 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-部署与现场方案](./10-部署与现场方案.md)。
**降级条件**:若现场主机无法运行 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) |
| 注释 | 只在解释「为什么」时写;不写复述代码的注释 |