- AP 概览页 Tab 工作台化(待付款/异常付款/风险标记/对账异常) - AP 导出端点支持 CSV/XLSX 与筛选 - 风险标记 resolve/dismiss 支持说明审计留痕 - 支付批次深链、高风险复核提示、对账确认权限收口 - OpenClaw 浏览器级回归全部通过
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
开发环境默认账号(仅用于本地 seed:dev):
- employee:
[email protected]/EmployeePassword1234 - manager:
[email protected]/ManagerPassword1234 - finance:
[email protected]/FinancePassword1234 - finance_mgr:
[email protected]/FinanceMgrPassword1234 - admin:
[email protected]/AdminPassword1234 - ceo:
[email protected]/CeoPassword1234
说明:
- 以上账号可通过
SEED_*环境变量覆盖(见server/src/scripts/seed-dev.js)。 - 生产/试运行环境默认采用“管理员重置密码”,不建议启用默认弱口令。
试运行管理员引导(E20):
cd /home/shunli/Projects/hyb-oa/server
TRIALRUN_ADMIN_EMAIL="[email protected]" \
TRIALRUN_ADMIN_PHONE="13800000000" \
TRIALRUN_ADMIN_PASSWORD="" \
npm run seed:trialrun
说明:
TRIALRUN_ADMIN_PASSWORD为空时会生成并打印一次性口令(请立刻保存)。- 若管理员已存在,脚本只确保其为 active 且具备
admin+employee角色,不会改动密码。
2.3.1 认证安全说明(P5)
POST /api/auth/login成功响应包含mustChangePassword:true:表示该账号处于“首次登录/管理员重置密码后”状态;前端应强制引导改密后再进入业务页。- 改密接口:
POST /api/auth/change-password(需要authorization: Bearer <token>+x-device-fingerprint)。
- 登录失败锁定(默认):
- 15 分钟内失败 5 次会锁定 30 分钟,返回
423 AUTH_002(含lockedUntil)。 - 解锁:
POST /api/users/:id/unlock(权限:system.users)。
- 15 分钟内失败 5 次会锁定 30 分钟,返回
- 弱密码拦截:
- 采用
zxcvbn评分,默认score < 2拒绝(返回VALID_001.details.password)。
- 采用
- 可通过
WEAK_PASSWORD_CHECK_ENABLED=false关闭(不建议生产关闭)。
2.3.2 邮件通知(可选,F15)
- 默认关闭:
EMAIL_ENABLED=false - 开启后支持触发邮件(失败降级不阻塞主流程):
- 新待办分配(workflow.task_assigned)
- 审批结果(approved/rejected/returned)
- 付款完成(reimbursement.paid)
- 密码重置(forgot-password / reset-password)
- 需要配置 SMTP:
SMTP_HOST、SMTP_PORT(默认 587)、SMTP_SECURE(465 通常为 true)SMTP_USER、SMTP_PASS(按 SMTP 服务要求)EMAIL_FROM(发件人地址)EMAIL_BASE_URL(可选,邮件内“查看详情”链接的站点前缀;为空则尝试使用APP_ORIGIN的第一个值)
2.3.3 作业系统(cron vs pg-boss)(F19~F19d)
- 默认:
JOB_DRIVER=cron(同进程 node-cron 调度) - 可选升级:
JOB_DRIVER=pgboss(Postgres 队列,适配单机/多进程;避免引入 Redis)PG_BOSS_SCHEMA(默认pgboss)JOBS_ENABLED=false可整体关闭作业(仅用于排障/紧急回滚)
- 目前迁移的作业:
ocr.tick(受OCR_PROVIDER控制,off 时不调度)delegation.revoke-expiredinvoice.archive-unused
- 回滚策略:
- 将
JOB_DRIVER切回cron(无需 schema 迁移回滚,风险低)
- 将
2.3.4 Worker 进程拆分(可选,F20)
目的:单机也能用“API 进程 + Worker 进程”模式运行,便于未来横向扩展;不改变对外 API。
- API 进程:只提供 HTTP(可选托管
client/dist),不跑作业 - Worker 进程:只跑作业(OCR/委派回收/超期归档),不监听端口
本地双进程示例:
# 终端 A:API(不跑作业)
cd /home/shunli/Projects/hyb-oa/server
JOBS_ENABLED=false npm run dev
# 终端 B:Worker(只跑作业)
cd /home/shunli/Projects/hyb-oa/server
JOBS_ENABLED=true npm run worker
试运行 compose(推荐,见 deploy/trialrun/runbook.md):
- 启用 worker profile:
--profile worker - 同时将 server 的
JOBS_ENABLED=false(worker 进程内强制为 true)
2.3.5 存储驱动(local vs S3/MinIO)(F21)
目标:把“票据文件存储”从本地目录可选演进到 S3 兼容(单机优先 MinIO),保持签名 URL 契约不变。
- 默认:
STORAGE_DRIVER=local(写入STORAGE_DIR) - 可选:
STORAGE_DRIVER=s3(写入 S3/MinIO)- 需要配置:
S3_ENDPOINT/S3_REGION/S3_BUCKET/S3_ACCESS_KEY_ID/S3_SECRET_ACCESS_KEY/S3_FORCE_PATH_STYLE - 为兼容混合存储,
invoice_pool.storage_path对 S3 会写成s3:<key>(无 schema 变更)
- 需要配置:
签名 URL 兼容策略:
/api/invoice-pool/:id/signed-url仍返回本系统 URL(带 token)/api/invoice-pool/:id/file内部按storage_path自动从本地或 S3 读取并返回文件流
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
也可直接探活:
curl -fsS http://127.0.0.1:3000/health
curl -fsS http://127.0.0.1:3000/ready
curl -fsS http://127.0.0.1:3000/metrics | head
说明:
/health:liveness(不查 DB)/ready:readiness(SELECT 1探活 DB,失败返回 503)/metrics:Prometheus 指标(可通过METRICS_ENABLED=false关闭)
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,报销=rejectedreturn:实例=terminated,报销=draftwithdraw:审批未处理前可撤回return后二次submit:创建新实例PERM_002:非 assignee 无法操作no_actionable_approver:全链无可执行审批人阻断negative_not_allowed:负金额防御manager skip:直属上级缺失时跳过并记录transfer:转审成功(任务改派 + 任务/转审记录/history 三处证据)transfer参数校验:reason<10 返回VALID_002transfer同人转审:返回VALID_001(same_assignee_not_allowed)transfer链路上限:同 task 第 4 次转审阻断(chain_limit_exceeded)transfer目标校验:目标审批人须 active 且具备expense.approvepay:财务付款成功(approved -> paid,发票bound -> archived(used_complete))pay权限:非财务返回PERM_001pay状态校验:非approved/paid返回VALID_001pay幂等:重复付款返回 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 + regression-audit.sh + regression-ocr.sh,适用于 CI 或发版前检查。
可选关闭审计/OCR:
cd /home/shunli/Projects/hyb-oa/server
RUN_AUDIT_REGRESSION=0 RUN_OCR_REGRESSION=0 ./scripts/regression-all.sh
可选打开组织/配置回归(org + settings):
cd /home/shunli/Projects/hyb-oa/server
RUN_ORG_SETTINGS_REGRESSION=1 ./scripts/regression-all.sh
可选打开认证安全回归(auth security):
cd /home/shunli/Projects/hyb-oa/server
RUN_AUTH_SECURITY_REGRESSION=1 ./scripts/regression-all.sh
独立执行:
cd /home/shunli/Projects/hyb-oa/server
./scripts/regression-audit.sh
OCR_PROVIDER=mock ./scripts/regression-ocr.sh
./scripts/regression-org-settings.sh
./scripts/regression-auth-security.sh
4.5 Postman 回归
- Collection:
server/postman/hyb-oa.postman_collection.json - Environment:
server/postman/hyb-oa.postman_environment.json - 核心链路:
- Login Employee
- Create Reimbursement
- Upload Invoice
- Bind Invoice
- Submit Reimbursement
- Login CEO
- Approve Workflow Task
- 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 续签。
4.7 单域托管 client/dist(可选,试运行建议)
cd /home/shunli/Projects/hyb-oa/client
npm run build
cd /home/shunli/Projects/hyb-oa/server
SERVE_CLIENT_DIST=true npm run dev
说明:
- 访问
http://127.0.0.1:3000即可打开前端(同域 cookie,减少 CORS 复杂度)。 - SPA 路由(如
/dashboard)会回退到index.html;/api/*不参与回退。
4.8 试运行部署包(Docker Compose)
试运行/上线演练建议使用仓库内置部署包:
deploy/trialrun/runbook.mddeploy/trialrun/docker-compose.ymldeploy/trialrun/.env.exampledeploy/trialrun/checklist.md
备份/恢复脚本(E14):
cd /home/shunli/Projects/hyb-oa
./scripts/backup-db.sh
./scripts/restore-db.sh deploy/trialrun/backups/<db.dump>
./scripts/backup-storage.sh
./scripts/restore-storage.sh deploy/trialrun/backups/<storage.tar.gz> deploy/trialrun/data/storage
优雅停机(E21):
docker stop/Ctrl+C会触发SIGTERM/SIGINT,server 会调用app.close()完成收敛关闭(定时任务/HTTP/DB)。
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 工作流查询增强(P3)
GET /api/workflow/tasks:支持status/page/pageSize,返回page/pageSize/total/items。GET /api/workflow/tasks/:taskId:任务详情(实例、报销摘要、可执行动作、处理历史)。GET /api/workflow/approvers:转审候选人列表(active + 具备expense.approve)。
5.6 部门管理(P5 蓝图 F26)
GET /api/departments:部门列表 + 树(登录即可;默认不含归档部门)- 可选:
GET /api/departments?includeArchived=1(包含归档部门)
- 可选:
POST /api/departments:创建部门(system.users)PATCH /api/departments/:id:更新部门(system.users;已归档禁止修改;禁止形成父子环)POST /api/departments/:id/archive:归档部门(system.users)- 归档约束:有子部门/有在职员工则禁止归档
- 幂等:已归档重复调用返回
outcome=already_archived
5.7 列表查询接口(P3)
GET /api/reimbursements:支持page/pageSize/status/scope。GET /api/invoice-pool:支持page/pageSize/status/ownerScope。
5.8 定时任务(可手动执行)
cd /home/shunli/Projects/hyb-oa/server
npm run job:revoke-expired-delegations
npm run job:ocr-once
5.9 账号与角色管理(E17,试运行运营)
GET /api/users:员工列表(分页/搜索)POST /api/users:创建员工(含初始密码)GET /api/users/:id:员工详情(管理员可看全部;员工可看自己;敏感字段仅返回脱敏)POST /api/users/:id/disable:禁用员工(tokenVersion+1 + 撤销 refresh)POST /api/users/:id/enable:启用员工(tokenVersion+1 + 撤销 refresh)POST /api/users/:id/reset-password:重置密码(返回一次性口令,tokenVersion+1)POST /api/users/:id/roles:设置角色(tokenVersion+1 + 撤销 refresh)GET /api/users/:id/sensitive:敏感字段明文查询(F25)PATCH /api/users/:id/sensitive:敏感字段更新(F25)
权限:
system.users(hr/admin;admin 具备*)system.users_sensitive(仅特权账号;用于解密查看/更新银行卡、身份证等)
6. 关键环境变量
DATABASE_URL: PostgreSQL 连接串JWT_SECRET: JWT & signed-url token 签名密钥FIELD_ENCRYPTION_KEY: 敏感字段加密密钥(AES-256-GCM;base64 32 bytes;用于银行卡/身份证等)APP_TIMEZONE: 默认Asia/ShanghaiSTORAGE_DIR: 默认generated/storage(相对server/)SERVE_CLIENT_DIST: 是否由 server 托管client/dist(默认false)CLIENT_DIST_DIR: 前端构建产物目录(默认../client/dist,相对server/)SIGNED_URL_TTL_SECONDS: 默认600MAX_UPLOAD_SIZE_BYTES: 默认10485760(10MB)RATE_LIMIT_ENABLED: 是否启用关键接口限流(默认:production=true,其它环境=false)RATE_LIMIT_LOGIN_MAX:/api/auth/login等接口限流次数(默认10)RATE_LIMIT_LOGIN_WINDOW: 限流时间窗(默认1 minute,支持 ms 格式如30s/1m)DELEGATION_REVOKE_CRON: 委派自动回收定时表达式(默认0 * * * *)OIDC_ENABLED: 是否启用 OIDC 登录(可选;默认false)OIDC_ISSUER_URL: OIDC issuer URL(发现地址)OIDC_CLIENT_ID/OIDC_CLIENT_SECRET: OIDC client 配置OIDC_REDIRECT_URI: 回调地址(例如http://127.0.0.1:3000/api/auth/oidc/callback)OIDC_SCOPES: 默认openid profile emailOCR_PROVIDER:off/mock/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 配置
安全基线(P4):
- 安全头:默认启用
@fastify/helmet(CSP 默认关闭,避免误伤 SPA 本地调试)。 - 限流:
/api/auth/login、/api/auth/forgot-password、/api/auth/reset-password在RATE_LIMIT_ENABLED=true时启用限流。 - 日志脱敏:
authorization/cookie/password/code/newPassword等敏感字段会被 redaction。
回滚策略:
- 若限流误伤,可先设置
RATE_LIMIT_ENABLED=false(无需改代码)。 - 若安全头造成兼容性问题,可回退
server/src/app.js中 helmet 注册逻辑。
生产提示:大表加索引需评估
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、mock未开启或腾讯密钥无效 |
检查 OCR 环境变量,优先用 OCR_PROVIDER=mock 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 冒烟。
9. P6-P10 增量能力(验真 / 看板 / 预算 / 集成 / 生产硬化)
9.1 特性开关(默认值)
INVOICE_VERIFICATION_ENABLED=falseVERIFICATION_PROVIDER=mockBUDGET_CONTROL_MODE=warnERP_SYNC_ENABLED=falseERP_SYNC_PROVIDER=mockERP_SYNC_ENDPOINT=ERP_SYNC_TIMEOUT_MS=5000ERP_SYNC_AUTH_TOKEN=TRAVEL_SYNC_ENABLED=falseIM_NOTIFIER_ENABLED=false
9.1.1 Feature Flag 治理(P11)
- 新增统一管理 API:
GET /api/system/feature-flagsPUT /api/system/feature-flags/:key
- 首期纳管键:
erp_sync、invoice_verification。 - 优先级规则:
Env硬闸>DB在线灰度。即使 DB 放开,若 Env 为 false 仍会关闭。 - 部门灰度模式:
rolloutType=department_allowlist时,仅白名单部门生效。
建议:先在测试组织灰度开启,再逐步扩大范围。
9.2 新增 API 摘要
- 发票验真:
POST /api/invoice-pool/:id/verifyGET /api/invoice-pool/:id/verificationPOST /api/invoice-pool/:id/verification/retry
- 看板聚合:
GET /api/dashboard/expense-overviewGET /api/dashboard/trendsGET /api/dashboard/top-categoriesGET /api/dashboard/approval-efficiency
- 预算管理:
GET /api/budgetsPOST /api/budgetsPATCH /api/budgets/:idPOST /api/budgets/:id/archive
- 外部集成:
POST /api/integrations/erp/sync/:reimbursementIdPOST /api/travel/orders/importGET /api/notifications/channelsPOST /api/notifications/channels/test
9.3 新增回归脚本
cd /home/shunli/Projects/hyb-oa/server
./scripts/regression-verification.sh
./scripts/regression-dashboard.sh
./scripts/regression-budget.sh
./scripts/regression-integrations.sh
./scripts/regression-feature-flags.sh
可通过总回归开关启用:
cd /home/shunli/Projects/hyb-oa/server
RUN_VERIFICATION_REGRESSION=1 \
RUN_DASHBOARD_REGRESSION=1 \
RUN_BUDGET_REGRESSION=1 \
RUN_INTEGRATIONS_REGRESSION=1 \
RUN_FEATURE_FLAGS_REGRESSION=1 \
./scripts/regression-all.sh
9.4 单次任务脚本(调试)
cd /home/shunli/Projects/hyb-oa/server
VERIFICATION_RECORD_ID=<uuid> npm run job:verification-once
npm run job:integration-outbox-once
9.5 回滚建议
- 先关闭特性开关(验真/ERP/差旅/IM)。
- 再停止相关任务调度(verification / integration outbox)。
- 不回滚审计与历史台账数据,保持可追溯。
9.6 P9/P10 维护性与硬化说明
- 票据路由拆分:验真端点已拆到
src/routes/invoicePool/verification.js,读路径端点已拆到src/routes/invoicePool/read.js,主入口仍为src/routes/invoicePool.js,API 路径不变。 - Shared 抽象:
src/routes/invoicePool/shared/{selects,mappers,schemas,helpers}.js统一验真映射与票据查询select,避免字段漂移。 - 看板聚合:
/api/dashboard/trends与/api/dashboard/top-categories已改为数据库聚合(groupBy),降低大数据量下内存聚合开销。 - 看板回归:
./scripts/regression-dashboard.sh新增重复请求一致性断言;可通过DASHBOARD_PERF_CHECK=1打印趋势接口耗时探针。 - ERP硬化回归:
./scripts/regression-integrations.sh内嵌最小 Node Mock ERP 服务,覆盖 success / 4xx(dead_letter) / 5xx(retry) / timeout(retry)。 - 矩阵门禁:
scripts/validate-feature-matrix.py默认校验 P5 + P6-P8 + P9 + P10 四事实源;./scripts/check-minimal.sh会自动执行该校验。