决策 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>
66 lines
5.1 KiB
Markdown
66 lines
5.1 KiB
Markdown
# 运营后台接口(/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"
|
||
```
|