feat(server): 试运行部署与运营基线(E13/E14/E17/E19/E20/E21)

- 增加 deploy/trialrun:Docker Compose + env 模板 + runbook + 发布/回滚 checklist

- 新增 DB/文件存储备份恢复脚本(backup/restore)

- 新增 system.users 运营接口:创建/启用禁用/重置密码/分配角色 + 审计

- 新增 seed:trialrun 引导管理员账号(一次性口令)

- 补齐 SIGTERM/SIGINT 优雅停机(关闭 job/HTTP/DB)
这个提交包含在:
2026-02-09 01:12:49 +08:00
父节点 89c0ed2ec1
当前提交 4223d81eb4
共修改 19 个文件,包含 1385 行新增和 1 行删除
+60
查看文件
@@ -0,0 +1,60 @@
## hyb-oa 试运行环境变量示例(单机)
##
## 使用方式:
## cp deploy/trialrun/.env.example deploy/trialrun/.env
## 然后按需修改 deploy/trialrun/.env 中的值(不要提交 .env)
##
# ===== 基础域名(用于 refresh cookie 的 origin 校验)=====
# 单域托管模式下,前端与后端同域访问(建议生产/试运行)。
APP_ORIGIN="http://127.0.0.1:3000"
# ===== 服务端密钥(必须修改)=====
JWT_SECRET="change_me_in_trialrun"
# ===== 数据库(Compose 内部连接)=====
POSTGRES_DB="hyb_oa"
POSTGRES_USER="hyboa_user"
POSTGRES_PASSWORD="hyboa_password"
DATABASE_URL="postgresql://hyboa_user:hyboa_password@db:5432/hyb_oa?schema=public"
# ===== 存储(票据上传目录)=====
# 在 deploy/trialrun/docker-compose.yml 中映射到宿主机 deploy/trialrun/data/storage
STORAGE_DIR="/data/storage"
# ===== 单域托管 client/dist =====
SERVE_CLIENT_DIST="true"
CLIENT_DIST_DIR="../client/dist"
# ===== 网络与端口 =====
HOST="0.0.0.0"
PORT="3000"
# ===== Cookie 安全(HTTPS 反代时建议 true)=====
COOKIE_SECURE="false"
# ===== 安全基线(试运行建议打开)=====
RATE_LIMIT_ENABLED="true"
RATE_LIMIT_LOGIN_MAX="10"
RATE_LIMIT_LOGIN_WINDOW="1 minute"
# ===== OCR(试运行可先用 mock)=====
OCR_PROVIDER="mock" # off|mock|tencent
OCR_MAX_ATTEMPTS="3"
OCR_POLL_CRON="*/5 * * * * *" # 试运行/演练可用秒级;生产建议分钟级
OCR_BATCH_SIZE="10"
OCR_LOCK_TTL_SECONDS="300"
OCR_TIMEOUT_MS="5000"
TENCENT_SECRET_ID=""
TENCENT_SECRET_KEY=""
TENCENT_OCR_REGION="ap-beijing"
# ===== 委派回收 =====
DELEGATION_REVOKE_CRON="0 * * * *"
# ===== 试运行引导管理员(E20)=====
TRIALRUN_ADMIN_EMAIL="[email protected]"
TRIALRUN_ADMIN_PHONE="13800000000"
# 留空则首次 seed 自动生成并打印一次性口令
TRIALRUN_ADMIN_PASSWORD=""
+24
查看文件
@@ -0,0 +1,24 @@
FROM node:20-alpine AS base
WORKDIR /app
FROM base AS client_deps
COPY client/package.json client/package-lock.json ./client/
RUN cd client && npm ci
FROM client_deps AS client_build
COPY client ./client
RUN cd client && npm run build
FROM base AS server_deps
COPY server/package.json server/package-lock.json ./server/
COPY server/prisma ./server/prisma
RUN cd server && npm ci
FROM server_deps AS server_build
COPY server ./server
COPY --from=client_build /app/client/dist ./client/dist
WORKDIR /app/server
ENV NODE_ENV=production
CMD ["npm", "start"]
+43
查看文件
@@ -0,0 +1,43 @@
# hyb-oa 试运行发布 Checklist(E19)
> 目标:一次发布可追踪、可回归、可回滚(10 分钟内恢复到上个版本)。
## A. 发布前(Pre-flight)
- [ ] 确认当前版本已打 tag:`git tag -a trialrun-YYYYMMDD-NN -m "trialrun release"`
- [ ] 本地门禁:`./scripts/check-minimal.sh`
- [ ] 全量回归(建议):`cd server && RUN_AUDIT_REGRESSION=1 RUN_OCR_REGRESSION=1 ./scripts/regression-all.sh`
- [ ] 备份(DB + 文件存储):
- [ ] `set -a && source deploy/trialrun/.env && set +a`
- [ ] `./scripts/backup-db.sh`(记录输出文件名)
- [ ] `./scripts/backup-storage.sh`(记录输出文件名)
## B. 发布(Deploy)
- [ ] `git checkout <tag>`
- [ ] 构建并启动:
- [ ] `docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server`
- [ ] `docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm init`
- [ ] 探活:
- [ ] `curl -fsS http://127.0.0.1:3000/ready`
- [ ] 登录验证(管理员账号):
- [ ] 访问 `http://127.0.0.1:3000`
- [ ] 登录成功,菜单权限正确
## C. 发布后(Post-check)
- [ ] 关键链路手工验证(建议至少一次):
- [ ] 员工:新建报销 → 上传票据 → 绑定 → 提交
- [ ] 经理:待办审批(approve)
- [ ] 财务:付款(含凭证字段)
- [ ] 查看 server 日志(无异常错误风暴):
- [ ] `docker logs --tail=200 hyb-oa-trialrun-server`
## D. 回滚(Rollback)
> 原则:如果涉及迁移,最快回滚通常是 “恢复备份 + 回到上个 tag”。
- [ ] 停止 server:`docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env stop server`
- [ ] 恢复 DB:`./scripts/restore-db.sh <db.dump>`
- [ ] 恢复存储:`./scripts/restore-storage.sh <storage.tar.gz> deploy/trialrun/data/storage`
- [ ] 回到上个版本:`git checkout <previous-tag>`
- [ ] 重启并探活:
- [ ] `docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server`
- [ ] `curl -fsS http://127.0.0.1:3000/ready`
+102
查看文件
@@ -0,0 +1,102 @@
version: "3.8"
services:
db:
image: postgres:16-alpine
container_name: hyb-oa-trialrun-db
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "5432:5432"
volumes:
- ./data/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
server:
build:
context: ../..
dockerfile: deploy/trialrun/Dockerfile
container_name: hyb-oa-trialrun-server
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
NODE_ENV: production
APP_ORIGIN: ${APP_ORIGIN}
JWT_SECRET: ${JWT_SECRET}
DATABASE_URL: ${DATABASE_URL}
STORAGE_DIR: ${STORAGE_DIR}
HOST: ${HOST}
PORT: ${PORT}
COOKIE_SECURE: ${COOKIE_SECURE}
SERVE_CLIENT_DIST: ${SERVE_CLIENT_DIST}
CLIENT_DIST_DIR: ${CLIENT_DIST_DIR}
RATE_LIMIT_ENABLED: ${RATE_LIMIT_ENABLED}
RATE_LIMIT_LOGIN_MAX: ${RATE_LIMIT_LOGIN_MAX}
RATE_LIMIT_LOGIN_WINDOW: ${RATE_LIMIT_LOGIN_WINDOW}
OCR_PROVIDER: ${OCR_PROVIDER}
OCR_MAX_ATTEMPTS: ${OCR_MAX_ATTEMPTS}
OCR_POLL_CRON: ${OCR_POLL_CRON}
OCR_BATCH_SIZE: ${OCR_BATCH_SIZE}
OCR_LOCK_TTL_SECONDS: ${OCR_LOCK_TTL_SECONDS}
OCR_TIMEOUT_MS: ${OCR_TIMEOUT_MS}
TENCENT_SECRET_ID: ${TENCENT_SECRET_ID}
TENCENT_SECRET_KEY: ${TENCENT_SECRET_KEY}
TENCENT_OCR_REGION: ${TENCENT_OCR_REGION}
DELEGATION_REVOKE_CRON: ${DELEGATION_REVOKE_CRON}
ports:
- "3000:3000"
volumes:
- ./data/storage:${STORAGE_DIR}
init:
build:
context: ../..
dockerfile: deploy/trialrun/Dockerfile
container_name: hyb-oa-trialrun-init
depends_on:
db:
condition: service_healthy
environment:
NODE_ENV: production
APP_ORIGIN: ${APP_ORIGIN}
JWT_SECRET: ${JWT_SECRET}
DATABASE_URL: ${DATABASE_URL}
STORAGE_DIR: ${STORAGE_DIR}
SERVE_CLIENT_DIST: ${SERVE_CLIENT_DIST}
CLIENT_DIST_DIR: ${CLIENT_DIST_DIR}
OCR_PROVIDER: ${OCR_PROVIDER}
OCR_POLL_CRON: ${OCR_POLL_CRON}
OCR_BATCH_SIZE: ${OCR_BATCH_SIZE}
OCR_LOCK_TTL_SECONDS: ${OCR_LOCK_TTL_SECONDS}
OCR_MAX_ATTEMPTS: ${OCR_MAX_ATTEMPTS}
OCR_TIMEOUT_MS: ${OCR_TIMEOUT_MS}
TENCENT_SECRET_ID: ${TENCENT_SECRET_ID}
TENCENT_SECRET_KEY: ${TENCENT_SECRET_KEY}
TENCENT_OCR_REGION: ${TENCENT_OCR_REGION}
DELEGATION_REVOKE_CRON: ${DELEGATION_REVOKE_CRON}
TRIALRUN_ADMIN_EMAIL: ${TRIALRUN_ADMIN_EMAIL}
TRIALRUN_ADMIN_PHONE: ${TRIALRUN_ADMIN_PHONE}
TRIALRUN_ADMIN_PASSWORD: ${TRIALRUN_ADMIN_PASSWORD}
volumes:
- ./data/storage:${STORAGE_DIR}
entrypoint: ["sh", "-c"]
command: >
"cd /app/server
&& npm run prisma:migrate:deploy
&& npm run seed:config
&& npm run seed:trialrun"
restart: "no"
+22
查看文件
@@ -0,0 +1,22 @@
# hyb-oa 反向代理示例(Nginx)
#
# 说明:
# - 适用于单机试运行:Nginx(443) -> hyb-oa server(3000)
# - server 内已启用 `trustProxy: true`,会读取 X-Forwarded-*。
#
server {
listen 80;
server_name oa.example.com;
# 试运行可以先走 http;生产建议配 TLS 并把 COOKIE_SECURE=true
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
+136
查看文件
@@ -0,0 +1,136 @@
# hyb-oa 试运行 Runbook(单机)
> 目标:按本文档在一台新机器完成 **启动 → 迁移 → 初始化管理员 → 验收 → 备份 → 恢复 → 回滚** 演练。
## 0. 前置条件
- Docker + Docker Compose(v2)
- 机器时间正确(影响 token / cron / OCR)
## 1. 首次启动(推荐:单域托管)
### 1.1 准备环境变量
```bash
cd /path/to/hyb-oa
cp deploy/trialrun/.env.example deploy/trialrun/.env
```
必须修改:
- `JWT_SECRET`
- `APP_ORIGIN`(建议与对外域名一致,例如 `https://oa.example.com`)
### 1.2 启动数据库与服务
```bash
cd /path/to/hyb-oa
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d db
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server
```
### 1.3 初始化(迁移 + 配置 seed + 试运行管理员)
```bash
cd /path/to/hyb-oa
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm init
```
说明:
- `init` 是一次性任务:`migrate:deploy + seed:config + seed:trialrun`。
- 若 `TRIALRUN_ADMIN_PASSWORD` 为空,seed 会生成一次性口令并输出到控制台(请立刻保存)。
### 1.4 验收
```bash
curl -fsS http://127.0.0.1:3000/health
curl -fsS http://127.0.0.1:3000/ready
```
浏览器访问:
- `http://127.0.0.1:3000`(单域托管前端)
## 1.5 备份与恢复演练(强烈建议至少做一次)
> 备份建议做到“DB + 文件存储(票据上传目录)”双份齐全。
### 1.5.1 备份
```bash
cd /path/to/hyb-oa
set -a
source deploy/trialrun/.env
set +a
./scripts/backup-db.sh
./scripts/backup-storage.sh
```
输出默认在:
- `deploy/trialrun/backups/`
### 1.5.2 恢复(到同一台机器或新机器)
1) 先停止 server:
```bash
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env stop server
```
2) 恢复 DB:
```bash
set -a
source deploy/trialrun/.env
set +a
./scripts/restore-db.sh deploy/trialrun/backups/<hyb-oa-db-*.dump>
```
3) 恢复文件存储:
```bash
./scripts/restore-storage.sh deploy/trialrun/backups/<hyb-oa-storage-*.tar.gz> deploy/trialrun/data/storage
```
4) 重启并验收:
```bash
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d server
curl -fsS http://127.0.0.1:3000/ready
```
## 2. 升级流程(试运行)
建议版本化方式:
- 每次发布打 tag,例如 `trialrun-2026-02-08-01`
步骤:
1) 先做备份(见 E14 脚本)
2) 拉取代码:`git pull` / `git checkout <tag>`
3) 构建并重启:
```bash
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env run --rm init
```
发布/回滚 Checklist:
- `deploy/trialrun/checklist.md`
## 3. 回滚流程(试运行)
> 回滚分两类:仅回滚代码(无迁移),或需要 DB 回滚(恢复备份)。
### 3.1 仅回滚代码
```bash
git checkout <previous-tag>
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env up -d --build server
```
### 3.2 需要 DB 回滚(推荐做法)
1) 停止服务:
```bash
docker compose -f deploy/trialrun/docker-compose.yml --env-file deploy/trialrun/.env stop server
```
2) 按 E14 恢复 DB + 文件存储备份
3) 启动服务并验收 `/ready` 与登录
## 4. 日志与排障
- server 容器日志:
```bash
docker logs -f hyb-oa-trialrun-server
```
- `/ready` 失败通常为 DB 不可达或连接串错误。
## 5. 优雅停机(E21)
说明:
- `docker compose stop server` 会发送 `SIGTERM`,server 会尝试在 10s 内完成:
- 停止 OCR/委派回收定时任务
- 关闭 HTTP 监听
- 断开 Prisma/PG 连接
- 超时会强制退出(避免卡死导致升级失败)。