170 lines
7.5 KiB
Markdown
170 lines
7.5 KiB
Markdown
# 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) |
|
||
| 注释 | 只在解释「为什么」时写;不写复述代码的注释 |
|