# hyb-oa Server Runbook (P0/P1) ## 1. 环境准备 - Node.js: `>=20` - Docker + Docker Compose - 本地工具:`curl`、`jq` - 配置文件:从 `server/.env.example` 复制为 `server/.env` 并按需修改 ```bash cd /home/shunli/Projects/hyb-oa/server cp .env.example .env ``` ## 2. Gate 基线命令(必须顺序执行) ### 2.1 启动数据库 ```bash cd /home/shunli/Projects/hyb-oa docker compose up -d db ``` ### 2.2 应用迁移 ```bash cd /home/shunli/Projects/hyb-oa/server npm run prisma:migrate:deploy ``` ### 2.3 执行 seed(幂等) ```bash cd /home/shunli/Projects/hyb-oa/server npm run seed:dev ``` ### 2.4 启动服务 ```bash cd /home/shunli/Projects/hyb-oa/server npm run dev ``` ### 2.5 冒烟验证 ```bash cd /home/shunli/Projects/hyb-oa/server ./scripts/smoke-test.sh ``` ## 3. 1 分钟快速验证 ```bash cd /home/shunli/Projects/hyb-oa/server ./scripts/smoke-test.sh ``` --- ## 4. 回归入口 ### 4.1 脚本回归(推荐) ```bash cd /home/shunli/Projects/hyb-oa/server BASE_URL=http://127.0.0.1:3000 ./scripts/smoke-test.sh ``` ### 4.2 判重专项回归(dedup) ```bash cd /home/shunli/Projects/hyb-oa/server DATABASE_URL='postgresql://hyboa_user:hyboa_password@127.0.0.1:5432/hyb_oa?schema=public' \ BASE_URL=http://127.0.0.1:3000 ./scripts/regression-dedup.sh ``` ### 4.3 工作流动作回归 ```bash cd /home/shunli/Projects/hyb-oa/server ./scripts/regression-workflow-actions.sh ``` 覆盖场景: - `reject`:实例=`rejected`,报销=`rejected` - `return`:实例=`terminated`,报销=`draft` - `withdraw`:审批未处理前可撤回 - `return` 后二次 `submit`:创建新实例 - `PERM_002`:非 assignee 无法操作 - `no_actionable_approver`:全链无可执行审批人阻断 - `negative_not_allowed`:负金额防御 - `manager skip`:直属上级缺失时跳过并记录 - `transfer`:转审成功(任务改派 + 任务/转审记录/history 三处证据) - `transfer` 参数校验:reason<10 返回 `VALID_002` - `transfer` 同人转审:返回 `VALID_001`(`same_assignee_not_allowed`) - `transfer` 链路上限:同 task 第 4 次转审阻断(`chain_limit_exceeded`) - `transfer` 目标校验:目标审批人须 active 且具备 `expense.approve` - `pay`:财务付款成功(`approved -> paid`,发票 `bound -> archived(used_complete)`) - `pay` 权限:非财务返回 `PERM_001` - `pay` 状态校验:非 `approved/paid` 返回 `VALID_001` - `pay` 幂等:重复付款返回 200(`archivedInvoiceCount` 可为 0) - `pay` 幂等冲突:已付款后提交冲突凭证字段返回 `409 VALID_001`(`already_paid_conflict`) - `delegation`:有效期内可代办并在 history 写入 delegation 证据 - `delegation` 过期:代理人返回 `PERM_002`,自动回收任务可撤销过期委派 ### 4.4 一键全量回归 ```bash cd /home/shunli/Projects/hyb-oa/server ./scripts/regression-all.sh ``` 串联 `smoke-test.sh + regression-dedup.sh + regression-workflow-actions.sh`,适用于 CI 或发版前检查。 ### 4.5 Postman 回归 - Collection: `server/postman/hyb-oa.postman_collection.json` - Environment: `server/postman/hyb-oa.postman_environment.json` - 核心链路: 1. Login Employee 2. Create Reimbursement 3. Upload Invoice 4. Bind Invoice 5. Submit Reimbursement 6. Login CEO 7. Approve Workflow Task 8. Get Signed URL ### 4.6 前端壳联调(P1-5) ```bash cd /home/shunli/Projects/hyb-oa/client npm install npm run dev ``` 说明: - 前端开发地址默认 `http://127.0.0.1:5173`。 - 通过 Vite 代理将 `/api` 转发到 `http://127.0.0.1:3000`。 - Access Token 仅保存在内存,Refresh Token 通过 HttpOnly Cookie 续签。 --- ## 5. P2 API 摘要 ### 5.1 付款标记(pay) - `POST /api/reimbursements/:id/pay` - 权限:`expense.finance.pay` - 可选请求体:`paymentVoucherNo`、`paymentDate(YYYY-MM-DD)`、`paymentChannel(bank/alipay/wechat/cash/other)` - 语义: - `approved -> paid`:返回 200,并归档关联 `bound` 发票 - 已 `paid` 重复同值:返回 200(`payment.idempotent=true`) - 已 `paid` 重复冲突值:返回 `409 VALID_001`,`details.payment=already_paid_conflict` ### 5.2 委派管理 - `POST /api/delegations`:创建委派(scope + validFrom/validTo) - `GET /api/delegations/mine`:查看我发起/我接收的委派 - `POST /api/delegations/:id/revoke`:撤销委派 - 委派仅作用于 `assignee_id` 任务,不改写 `assignee_role` 语义。 ### 5.3 OCR 异步状态查询 - 上传发票在 OCR 开启且 `ocr_type=vat` 时会进入 `pending_ocr`。 - `GET /api/invoice-pool/:id` 可轮询 OCR 处理状态(`pending_ocr/available/ocr_failed`)。 ### 5.4 审计日志查询 - `GET /api/audit-logs` - 权限:`system.audit.view`(finance_mgr/admin) - 支持分页与筛选(actor/action/targetType/date range)。 ### 5.5 定时任务(可手动执行) ```bash cd /home/shunli/Projects/hyb-oa/server npm run job:revoke-expired-delegations npm run job:ocr-once ``` --- ## 6. 关键环境变量 - `DATABASE_URL`: PostgreSQL 连接串 - `JWT_SECRET`: JWT & signed-url token 签名密钥 - `APP_TIMEZONE`: 默认 `Asia/Shanghai` - `STORAGE_DIR`: 默认 `generated/storage`(相对 `server/`) - `SIGNED_URL_TTL_SECONDS`: 默认 `600` - `MAX_UPLOAD_SIZE_BYTES`: 默认 `10485760`(10MB) - `DELEGATION_REVOKE_CRON`: 委派自动回收定时表达式(默认 `0 * * * *`) - `OCR_PROVIDER`: `off` 或 `tencent`(默认 `off`) - `OCR_BATCH_SIZE`: 每轮 OCR 认领数量(默认 `10`) - `OCR_LOCK_TTL_SECONDS`: OCR 锁超时秒数(默认 `300`) - `OCR_POLL_CRON`: OCR 轮询 cron(默认 `* * * * *`) - `OCR_MAX_ATTEMPTS`: OCR 最大重试次数(默认 `3`) - `OCR_TIMEOUT_MS`: 腾讯 OCR SDK 超时(默认 `5000`) - `TENCENT_SECRET_ID` / `TENCENT_SECRET_KEY` / `TENCENT_OCR_REGION`: 腾讯 OCR 配置 > 生产提示:大表加索引需评估 `CREATE INDEX CONCURRENTLY`,避免锁表影响。 --- ## 7. 常见失败排查 | 现象 | 可能原因 | 解决方案 | |---|---|---| | `prisma migrate deploy` 失败 | `DATABASE_URL` 未配置/不可达 | 检查 `server/.env` 与数据库容器状态 | | `npm run seed:dev` 重复执行报错 | seed 脚本被改坏,未保持幂等 | 检查 `seed-config.js`/`seed-dev.js` 的 `upsert` / `skipDuplicates` | | signed-url 接口返回 403 | `JWT_SECRET` 不一致、token 过期、访问人无权限 | 统一服务密钥、确认 URL 在 TTL 内、检查账号角色 | | 上传返回 `file_too_large` | 文件超 `MAX_UPLOAD_SIZE_BYTES` | 调整环境变量或使用更小文件 | | 上传失败且报目录/权限错误 | `STORAGE_DIR` 不存在或不可写 | 创建目录并修正权限;Docker 需确认 volume 挂载 | | OCR 长期停留 `pending_ocr` | `OCR_PROVIDER=off` 或密钥无效 | 检查 OCR 环境变量,手动执行 `npm run job:ocr-once` 验证 | | 委派过期后仍可操作 | 自动回收任务未运行 | 检查 `DELEGATION_REVOKE_CRON`,手动执行 `npm run job:revoke-expired-delegations` | --- ## 8. 手工故障注入(非阻塞) ### 8.1 `STORAGE_DIR` 不可写路径 - 临时将 `STORAGE_DIR` 指向只读目录,执行上传接口,确认错误可观测(日志+返回码)。 ### 8.2 磁盘空间不足(ENOSPC) - 在受控环境中制造磁盘满条件后重试上传。 - 预期:错误路径可观测(日志中能定位到 I/O 错误),不要求纳入日常 CI 冒烟。