feat: 生产化改造(服务端安装程序/PWA/Docker+HTTPS)并修复侧边栏点击无反应、SPA 静态托管 dotfiles 两个缺陷

- 修复 web-admin 与 desktop-client 侧边栏点击无反应:el-menu 的 index 是路径,不能当路由名使用
- 修复 Web 产物目录含点号目录(如 .cache)时 res.sendFile 因 dotfiles:ignore 返回 500
- 后端生产模式:静态托管 Web 端 + SPA 回退、helmet、限流(通用/登录)、trust proxy、
  /healthz 与 /readyz、请求耗时日志、生产环境变量强校验、首启动自动迁移与创建管理员、PID 文件
- Web 管理端 PWA:manifest + service worker + 图标,可在浏览器安装为应用
- 新增服务端 + Web 管理端 Windows 安装程序(内置 Node 运行时、启停与服务脚本、开机自启、卸载保留数据)
- 新增 Docker/Compose/Nginx 部署样例与 docs/production.md
- 新增 UI 回归测试:Web 端 Electron 驱动真实点击 7 项、客户端冒烟 11 项(含侧边栏点击与离线回退)
This commit is contained in:
LoveChengke committed 2026-09-12 00:20:21 +08:00
1 parent 6e4c7c1050
commit 5545ef6f55
46 files changed
+3020 -152

No files matched your search

+236
View File
@@ -0,0 +1,236 @@
# 生产部署指南
本文档覆盖三种上线形态,按适用场景选择:
| 形态 | 适用场景 | 产物 | 本机验证情况 |
| --------------------- | -------------------------------------------- | ------------------------------------------------------ | -------------------------------------- |
| **A. Windows 安装包** | 学校机房、教师办公电脑(单机即完整系统) | `release-server/班级小助手服务端-<版本>-x64-setup.exe` | ✅ 已完整实测(静默安装/启停/卸载/UI) |
| **B. Docker + MySQL** | 云服务器、多终端共享一套数据(推荐长期方案) | `deploy/Dockerfile` + `deploy/docker-compose.yml` | ⚠️ 文件已提供,本机无 Docker 未实测 |
| **C. 手动部署** | 已有 Node 环境的服务器、需要自定义 | `pnpm dist:server` 的免安装目录 | ✅ 免安装目录已实测(54 项端到端) |
---
## 一、Windows 安装包(形态 A)
### 1.1 生成安装包
```bash
pnpm dist:server # 首次会执行一次 npm install(约 2 分钟)
pnpm dist:server -- --reuse-deps # 迭代打包:复用已安装依赖,跳过 npm install
```
产物:
| 路径 | 说明 |
| ----------------------------------------------------- | ----------------------------------------------- |
| `release-server/classhelper-server/` | 免安装目录,可直接拷到任意 Windows x64 机器运行 |
| `release-server/班级小助手服务端-0.1.0-x64-setup.exe` | 安装程序(约 34 MB,NSIS) |
打包内容:内置 **Node 运行时**(目标机无需安装 Node.js)、后端产物、Web 管理端产物、
生产依赖(真实目录,非软链)、迁移 SQL、`.env`(随机 JWT 密钥)、启停脚本。
### 1.2 安装
双击安装程序即可,**无需管理员权限**(当前用户级安装):
- 安装目录:`%LOCALAPPDATA%\Programs\ClassHelperServer`
- 静默安装:`"班级小助手服务端-0.1.0-x64-setup.exe" /S`
- 开始菜单:启动服务 / 停止服务 / 重启服务 / 打开管理端 / 使用说明 / 卸载
- 桌面:`班级小助手服务端.lnk`(打开管理端)
- 自动加入当前用户开机自启(`HKCU\...\CurrentVersion\Run`)
- 卸载信息注册到「应用和功能」
安装完成后服务会自动启动并完成首次初始化:
1. 数据库为空 → 自动执行随包迁移(`AUTO_MIGRATE=true`)建表
2. 自动创建管理员账号(默认 `admin / admin123`,日志中会提示尽快修改)
3. 打开 http://127.0.0.1:4000/ 即为 Web 管理端
### 1.3 目录与配置
```
%LOCALAPPDATA%\Programs\ClassHelperServer\
├── node.exe 内置 Node 运行时
├── server\dist\ 后端程序(入口 index.js)
├── server\prisma\ 迁移 SQL(首启动自动执行)
├── web\ Web 管理端(由后端托管)
├── data\ classhelper.db + server.pid(备份只需复制此目录)
├── logs\server.log UTF-8 日志(含请求耗时)
├── .env 全部配置
├── start.cmd / stop.cmd / restart.cmd / status.cmd
└── install-service.cmd / uninstall-service.cmd
```
常用命令:
```cmd
start.cmd :: 启动并等待就绪(已在运行则直接返回)
stop.cmd :: 按 PID 精确停止
restart.cmd :: 重启
status.cmd :: 查看状态与 HTTP 探针结果
```
以管理员身份运行 `install-service.cmd` 可注册为**开机启动的计划任务**(等价于系统服务,
比注册表 Run 更稳,适合无人值守的机房主机):
```cmd
install-service.cmd :: schtasks /Create /SC ONSTART /RL HIGHEST
uninstall-service.cmd :: 删除计划任务
```
### 1.4 升级
1. `stop.cmd` 停止服务
2. 备份 `data` 目录
3. 运行新版本安装程序(**覆盖安装会保留原有 `.env`,JWT 密钥与端口设置不变,用户无需重新登录**)
4. `start.cmd` 启动(数据库结构如变更,会在首启动时自动补迁移)
### 1.5 卸载
开始菜单 → 卸载(或 `uninstall.exe`,静默:`uninstall.exe /S`)。
卸载会停止服务、删除快捷方式与注册表项、删除程序文件,并把 `data` 目录重命名为
**`data.backup` 保留**,避免误删数据库;确认不需要后再手动删除。
---
## 二、Docker + MySQL(形态 B,云部署推荐)
> 本机没有 Docker,因此这套文件未在本机实测;结构经过与已验证的产物对照(依赖清单、
> 目录布局、迁移方式与服务端完全一致)。
```bash
cp deploy/.env.example deploy/.env
# 生成 JWT 密钥并填入 deploy/.env
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"
docker compose -f deploy/docker-compose.yml --env-file deploy/.env up -d --build
```
组成:
| 服务 | 作用 |
| --------- | -------------------------------------------------------------------- |
| `db` | MySQL 8.4(utf8mb4、数据卷 `db-data`) |
| `migrate` | 一次性任务:安装 mariadb 适配器并执行 `prisma migrate deploy` |
| `server` | 后端 + Web 管理端(多阶段构建,仅含生产依赖),暴露 `${SERVER_PORT}` |
要点:
- 服务端容器 `NODE_ENV=production` + `STARTUP_DB_CHECK=true`:**数据库不可用会拒绝启动**
(避免带病对外服务);健康检查打 `/readyz`。
- 数据卷:MySQL 用 `db-data`;若坚持用 SQLite,把 `/app/data` 挂成卷即可(见 Dockerfile 注释)。
- 首次启动会按 `INITIAL_ADMIN_USERNAME/PASSWORD` 创建管理员(请在部署后立即改密码)。
- 反向代理上启用 HTTPS:见 `deploy/nginx.conf`,并在服务端环境变量中设置
`TRUST_PROXY=1`、`CORS_ORIGIN=https://你的域名`。
---
## 三、手动部署(形态 C)
```bash
pnpm install
pnpm build:shared
pnpm --filter @classhelper/server build
pnpm --filter @classhelper/web-admin build
pnpm dist:server -- --reuse-deps # 得到 release-server/classhelper-server 免安装目录
```
把该目录拷贝到目标机,编辑 `.env`(端口、数据库、密钥、跨域),然后:
```cmd
start.cmd :: Windows
# 或直接:node server/dist/index.js (Linux/macOS 用系统 node)
```
数据库初始化二选一:
- 让服务自愈:`.env` 中 `AUTO_MIGRATE=true`(SQLite 首次启动自动建表 + 建管理员)
- 显式迁移:`pnpm db:migrate` / `npx prisma migrate deploy`(MySQL 必须走这条)
---
## 四、环境变量清单
| 变量 | 默认 | 说明 |
| --------------------------------------------------- | ---------------------- | --------------------------------------------------------- |
| `NODE_ENV` | `development` | `production` 会启用生产校验、HSTS、更严的限流 |
| `HOST` / `PORT` | `0.0.0.0` / `4000` | 监听地址与端口 |
| `DATABASE_PROVIDER` | `sqlite` | `sqlite` 或 `mysql` |
| `DATABASE_URL` | `file:./prisma/dev.db` | SQLite 相对路径以 `server` 根为基准;MySQL 用连接串 |
| `AUTO_MIGRATE` | `false` | SQLite 空库时自动执行随包迁移并创建管理员 |
| `JWT_SECRET` | — | **必填**,≥32 位;生产环境仍为示例值会拒绝启动 |
| `JWT_EXPIRES_IN` | `7d` | 令牌有效期 |
| `CORS_ORIGIN` | `*` | 允许来源,多个用逗号分隔;生产建议写具体域名 |
| `TRUST_PROXY` | `false` | 反向代理层数(Nginx 填 `1`),影响限流与日志中的真实 IP |
| `RATE_LIMIT_ENABLED` | `true` | 通用限流(生产 600 次/5 分钟)+ 登录限流(20 次/10 分钟) |
| `STARTUP_DB_CHECK` | `true` | 启动校验数据库;生产不可用时直接退出 |
| `PID_FILE` | — | PID 文件路径(安装包启停脚本依赖),相对 `server` 根 |
| `WEB_DIST_DIR` | 自动探测 | Web 管理端产物目录(`../web-admin/dist` 或 `./web`) |
| `LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` |
| `DEFAULT_STUDENT_PASSWORD` | `123456` | 新增学生的默认密码 |
| `TERM_START_DATE` | — | 第 1 教学周的周一,用于"当前周次"与课表默认视图 |
| `INITIAL_ADMIN_USERNAME` / `INITIAL_ADMIN_PASSWORD` | `admin` / `admin123` | 空库首启动创建的管理员 |
---
## 五、运维
### 5.1 探针与监控
| 端点 | 用途 |
| ------------- | ---------------------------------------------------- |
| `/healthz` | 存活探针:进程活着即 200(容器 liveness / 守护进程) |
| `/readyz` | 就绪探针:数据库可连才 200,否则 503(负载均衡摘流) |
| `/api/health` | 详情:环境、数据库、已挂载模块、运行时长 |
### 5.2 日志
- 输出到 stdout/stderr,安装包形态由启停脚本重定向到 `logs\server.log`(UTF-8)。
- 访问日志含方法、路径、状态码与耗时;5xx 记 error、4xx 记 warn。
- 容器形态建议交给 Docker 日志驱动或采集到 ELK/Loki。
### 5.3 备份与恢复
| 形态 | 备份 | 恢复 |
| ------ | ------------------------------------------------------ | ------------------------ |
| SQLite | 停止服务后复制 `data/classhelper.db` | 替换该文件后启动 |
| MySQL | `mysqldump -u classhelper -p classhelper > backup.sql` | `mysql ... < backup.sql` |
建议:每日自动备份 + 保留 7 份;变更 `JWT_SECRET` 会导致所有用户重新登录,请勿在无必要时更换。
### 5.4 安全清单(上线前逐项确认)
- [ ] 修改 `admin` 与教师账号的默认密码
- [ ] `JWT_SECRET` 使用随机值(≥48 字节),且妥善保管
- [ ] 公网访问必须经 HTTPS 反向代理(`deploy/nginx.conf` 可直接用)
- [ ] `CORS_ORIGIN` 收敛到具体域名,不要留 `*`
- [ ] `TRUST_PROXY=1` 并确认限流按真实 IP 生效(`/api` 响应头有 `RateLimit`)
- [ ] 数据库账号使用最小权限,仅授权业务库
- [ ] 定期备份并验证可恢复
- [ ] 关注 `/readyz` 与 `logs/server.log` 的 5xx
---
## 六、故障排查
| 现象 | 排查方向 |
| ------------------ | ----------------------------------------------------------------------------- |
| 浏览器打不开管理端 | `status.cmd`;`logs\server.log` 是否有启动报错;端口是否被占用 |
| 启动即退出(生产) | 数据库不可用(`DATABASE_URL`/MySQL 未就绪)或 `JWT_SECRET` 未按生产要求配置 |
| 页面能开但接口 401 | 令牌过期或更换过 `JWT_SECRET`,重新登录 |
| 限流误伤(自己人) | 反向代理未设 `TRUST_PROXY=1`,导致所有请求都算作代理 IP |
| 实时推送不生效 | 反向代理未放行 `/socket.io/` 的 WebSocket 升级(见 nginx 样例) |
| 安装后服务没起来 | 查看 `logs\server.error.log`;`start.cmd` 会输出就绪等待结果 |
| 数据库文件损坏 | 从备份恢复 `data/classhelper.db`;SQLite 场景请确保同一时间只有一个服务实例 |
| 升级后需要重新登录 | 说明 `.env` 被覆盖(JWT 密钥变化);覆盖安装应保留 `.env`,请检查安装目录权限 |
---
## 七、性能与容量参考
- 单机 SQLite 形态适合**单校规模**(数千用户、每天数百次写操作),实测 54 项接口与实时推送全部达标。
- 多校区 / 高并发建议切 MySQL,并横向扩容服务端(Socket.IO 需要配置 Redis adapter 才能多实例广播,
当前为单实例广播;如需多实例请参考 `packages/server/src/realtime/socket.ts` 引入 `@socket.io/redis-adapter`)。
- 静态资源已设置 immutable 长缓存 + gzip;Web 管理端为 SPA,首屏包体约 900 KB(gzip 约 290 KB),
成绩页含 ECharts 独立分包,按需加载。