文件
hyb-oa/server/README.md
T
wangsli 0cd44a3d6a feat(server): 委派回收并发防重 + 最小门禁闸门
- delegationRevokeJob 改用 FOR UPDATE SKIP LOCKED + UPDATE RETURNING 原子认领
- regression-workflow-actions 新增 RUN_DELEGATION_CONCURRENCY_CHECK 并发校验开关
- README 补充最小门禁与并发语义说明
2026-02-08 11:38:58 +08:00

8.0 KiB
原始文件 Blame 文件历史

hyb-oa Server Runbook (P0/P1)

1. 环境准备

  • Node.js: >=20
  • Docker + Docker Compose
  • 本地工具:curl、jq
  • 配置文件:从 server/.env.example 复制为 server/.env 并按需修改
cd /home/shunli/Projects/hyb-oa/server
cp .env.example .env

2. Gate 基线命令(必须顺序执行)

2.1 启动数据库

cd /home/shunli/Projects/hyb-oa
docker compose up -d db

2.2 应用迁移

cd /home/shunli/Projects/hyb-oa/server
npm run prisma:migrate:deploy

2.3 执行 seed(幂等)

cd /home/shunli/Projects/hyb-oa/server
npm run seed:dev

2.4 启动服务

cd /home/shunli/Projects/hyb-oa/server
npm run dev

2.5 冒烟验证

cd /home/shunli/Projects/hyb-oa/server
./scripts/smoke-test.sh

2.6 本地最小门禁(P1-b)

cd /home/shunli/Projects/hyb-oa
./scripts/check-minimal.sh

说明:

  • 该脚本只做静态/构建级门禁,不启动服务与数据库。
  • 全量回归仍通过 server/scripts/regression-all.sh 执行。

3. 1 分钟快速验证

cd /home/shunli/Projects/hyb-oa/server
./scripts/smoke-test.sh

4. 回归入口

4.1 脚本回归(推荐)

cd /home/shunli/Projects/hyb-oa/server
BASE_URL=http://127.0.0.1:3000 ./scripts/smoke-test.sh

4.2 判重专项回归(dedup)

cd /home/shunli/Projects/hyb-oa/server
DATABASE_URL='postgresql://hyboa_user:[email protected]:5432/hyb_oa?schema=public' \
BASE_URL=http://127.0.0.1:3000 ./scripts/regression-dedup.sh

4.3 工作流动作回归

cd /home/shunli/Projects/hyb-oa/server
./scripts/regression-workflow-actions.sh

可选并发校验:

cd /home/shunli/Projects/hyb-oa/server
RUN_DELEGATION_CONCURRENCY_CHECK=1 ./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,自动回收任务可撤销过期委派
  • delegation 并发回收(可选):RUN_DELEGATION_CONCURRENCY_CHECK=1 时双并发只回收一次并仅写一条自动回收审计

4.4 一键全量回归

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)

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 语义。
  • 自动回收实现采用 FOR UPDATE SKIP LOCKED + UPDATE ... RETURNING,多实例并发下确保同一委派仅回收一次。

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 定时任务(可手动执行)

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 冒烟。