决策 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>
5.1 KiB
5.1 KiB
运营后台接口(/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, 权限点),没有权限返回 403forbidden;/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"