Files
bid-doctor/docs/architecture/ops-api.md
YANG JIANKUAN dd8c04001d
Some checks failed
ci / server (push) Has been cancelled
ci / web (push) Has been cancelled
ci / contract (push) Has been cancelled
feat(m6): 额度流水、套餐开通、数据删除、运营后台(M6 剩下的 P0)
决策 M6-1–M6-10(docs/architecture/m6-plan.md、docs/decisions.md)。

用户端:
- 额度与套餐页:本期额度、按功能分布、扣点规则、流水(逐条余额、关联项目和任务、导出 CSV)
- 开通 / 续费申请(对公转账,运营确认到账后开通),80% / 100% 提醒
- 专业版到期:整个账号只读、额度清零,到期前 30 / 7 / 0 天提醒(worker 每小时 billing_tick)
- 删除项目(彻底删除、销毁数据密钥),注销账号(企业名称 + 短信验证码)
- 通知铃铛、账号页

运营后台:
- 独立进程 bid-doctor ops(/ops/v1)和 apps/admin;用户名 + 密码 + TOTP + IP 白名单
- 开通申请(到账 / 开通 / 退回 / 发票)、企业查询和调额度、误报 / 漏报、失败任务、审计;不看业务内容
- 左侧菜单(标准 logo、可展开收起的分组、图标、待办数、环境标签)
- 运营人员角色只留结构:ops_admin.role、权限点、每个接口的检查点、菜单按权限过滤

数据库:迁移 0010(排队的订阅、申请单号、通知类型、billing_due)、0011(bd_ops 权限)、0012(运营角色)。
另修:取消解析 / 检查接口报 500;uuid7 进程内单调;data_key 销毁后不再新建;审计 None 存 SQL NULL。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:06:06 +08:00

5.1 KiB
Raw Blame History

运营后台接口(/ops/v1)

运营后台的接口不在用户端契约 openapi.yaml 里:它们只挂在 bid-doctor ops 进程上(决策 M6-2),前端 apps/admin 是唯一的调用方,类型在 apps/admin/src/api.ts 里手写。实现见 server/src/bid_doctor/ops/(console_api.py 路由、console.py 查询、auth.py 登录)。

安全

方面 做法
进程 和 api、worker 同一镜像的第三种进程,单独端口(开发 8019);部署时只对内网开放
来源地址 BD_OPS_ALLOWED_IPS(CIDR 列表)以外的请求一律 403 ops_ip_denied;生产环境不配或放开全部地址拒绝启动
登录 用户名 + 密码(scrypt)+ TOTP 6 位动态码(身份验证器 App);同一用户名 15 分钟最多试 5 次;账号只能用 bid-doctor ops-admin create 建
会话 cookie bd_ops_session(HttpOnly、SameSite=Strict,8 小时)+ 写操作带 X-CSRF-Token(等于 cookie bd_ops_csrf)
数据库 跨企业查询用 bd_ops(绕过行级安全策略,业务表只读,运营账号和会话表可写,审计可写);对某个企业的修改走 bd_app + 那个企业的租户事务。bd_app 读不到运营账号表
留痕 每个操作都写审计(actor_kind = ops,记运营账号),查看企业详情也记一条 ops.tenant.view
内容 不显示项目名称、招标 / 投标原文、审计的修改前后(决策 M6-7、红线 10)

角色和权限点(只留结构,决策 M6-10)

以后会有管理员、运营、销售、运维等不同权限的运营人员。现在只建结构:

  • ops_admin.role(迁移 0012,不加 CHECK);
  • 权限点 OpsPermission(ops/auth.py):tenant.view、quota.adjust、activation.manage、invoice.manage、feedback.view、jobs.view、audit.view;
  • 「角色 → 权限点」对应表 ROLE_PERMISSIONS:现在只有 admin(全部权限)。表里有、对应表里没有的角色什么权限都没有;
  • 每个接口开头都调 auth.require(op, 权限点),没有权限返回 403 forbidden;/me 返回角色和权限点,前端菜单按权限点过滤(apps/admin/src/nav.ts),一组都看不到时整组不显示。

加角色时只改 ROLE_PERMISSIONS、ROLE_LABEL 和 ops-admin create --role 的说明;要不要有「运营账号管理」页面、谁能建账号,到时再定。

菜单(决策 M6-9)

左侧菜单由 apps/admin/src/nav.ts 一处配置:分组(客户、质量、安全)→ 菜单项(图标、路由、要的权限点、待办数)。分组可以展开收起,记在本机;跳到收起分组里的页面时自动展开。待办数来自 GET /summary(待处理的开通申请、近 7 天失败任务、近 7 天反馈),做完操作和每分钟刷新一次。

接口

方法 路径 说明
POST /auth/login {username, password, totp};失败 401 ops_login_failed
POST /auth/logout
GET /me 当前运营账号:角色、权限点、运行环境(非生产环境时菜单顶部提示)
GET /summary 菜单上的待办数;没有权限的那一项给 0
GET /tenants?q= 企业名称(模糊)、手机号(精确,按 HMAC 查)、申请单号;不带条件列最近注册的 50 个
GET /tenants/{id} 成员(手机号脱敏)、套餐历史、最近 30 条额度流水(不带项目名称)、任务按状态计数
POST /tenants/{id}/quota-adjustments {unit: parse, amount, reason};原因必填,不能扣成负数(G6)
GET /activation-requests?status= 待处理(等待转账、已到账)在前
POST /activation-requests/{id}/paid 标记到账(只限等待转账)
POST /activation-requests/{id}/activate {months = 12};免费版 / 已过期马上开始新一期,专业版没到期时新一期排队(续费)
POST /activation-requests/{id}/reject {note},用户在额度页看得到原因
POST /activation-requests/{id}/invoice {invoice_no},开通以后才能登记
GET /feedback?kind= 误报 / 漏报:标题、级别、理由、规则编号、规则集和提示词版本、页码
GET /failed-jobs 解析、检查、报告失败的任务:错误类别、错误码、阶段、耗时、模型调用次数
GET /audit-events?tenant_id=&action= 动作、对象、操作人、时间;action 按前缀筛选

本地跑起来

cd server && ../.venv/bin/bid-doctor ops            # :8019
../.venv/bin/bid-doctor ops-admin create --username dev --display-name 开发运营   # 打印 otpauth:// 地址,用身份验证器 App 添加
pnpm dev:admin                                      # :5174,/ops 代理到 :8019

已有的数据卷是在加 bd_ops 之前初始化的,要手工建一次角色(新数据卷由 deploy/compose/postgres/init-roles.sh 建):

docker exec -e PGPASSWORD=dev-owner bid-doctor-postgres-1 psql -U bd_owner -d bid_doctor \
  -c "CREATE ROLE bd_ops LOGIN BYPASSRLS PASSWORD 'dev-ops'" -c "GRANT CONNECT ON DATABASE bid_doctor TO bd_ops"