决策 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>
13 KiB
M6 剩下的 P0:额度流水、套餐开通、数据删除、运营后台(实现计划 v0.1)
依据:FR-M6-03 / 05 / 06 / 07 / 09(docs/analysis/03-functional-requirements.md),J4、J5(02-user-journeys.md),G6、G8、G10,决策 Q9、Q13、P1、DM4;表结构已在基线迁移里(data-model.md §4、§6),用户端接口已在 openapi.yaml 里定义;运营后台的接口和选型「做到时再定」(架构 §3.11、§4.3)。
0. 现状
| 方面 | 已有 | 还没有 |
|---|---|---|
| 额度 | 注册发放、预扣 / 结算 / 退回 / 免扣、GET /quota、顶栏额度(≥ 80% 赭色) |
流水接口和额度页、80% / 100% 提醒、人工调整、过期 |
| 套餐 | 免费版注册即开通、GET /plans、专业版套餐(解析 500,D2 待定) |
开通 / 续费申请、运营开通、到期处理 |
| 删除 | 资质删除(QL6) | 删除项目、注销账号 |
| 通知 | 解析、检查、报告完成写通知;事件流 | 前端通知入口、标为已读 |
| 运营后台 | 表:ops_admin、ops_session;角色 bd_ops(文档里有,部署脚本里还没建);反馈写入 |
进程、登录、接口、前端,全部没有 |
另外发现一处冲突:原型 project.html 的删除写「30 天内可以在回收站恢复」,而 G10、DM4(已确认)是删除时销毁项目数据密钥、彻底删除。按决策优先,见 M6-1。
1. 用户端
1.1 额度页(FR-M6-03,原型 quota.html)
- 侧栏账户区(头像、套餐标签)点开进「额度与套餐」页;顶栏额度改成链接,也进这个页。
- 页面:本期额度卡(剩余 / 已用 / 退回,期间起止)→ 扣点规则表(原型原样)→ 按功能分布(读标招标文件、补遗和澄清、查标、免扣次数)→ 本期流水。
- 流水:
GET /quota/entries,按单位筛选、按时间倒序分页;每行写时间、类型(发放 / 预扣 / 结算 / 退回 / 免扣 / 过期 / 调整)、数量(+ / −,墨色,Q1)、余额、关联项目 · 任务(「招标文件」「第 2 次检查」;项目删了写「已删除的项目」和任务编号,G10)、说明(调整原因、失败原因、「距上次扣额度的检查不到 24 小时」)。导出 CSV 在前端生成。 - 提醒:预扣后余额跨过 80%、100%(按本期发放算)时写站内通知,
quota_alert保证每期每档只提醒一次。
1.2 开通与续费(FR-M6-05,G8)
- 免费版:额度页和顶栏「额度用完」提示里有「开通专业版」;专业版:到期前 30 天起显示「续费」。
- 申请对话框(原型):企业名称(注册时的,只读)、发票类型(普票 / 专票)、纳税人识别号、联系人、手机号;专票再加地址电话、开户行账号。
POST /activation-requests,不扣额度、不用先付款。 - 提交后页面顶部显示申请单号、状态(等待对公转账 / 已到账 / 已开通 / 已退回)、收款信息(收款单位、开户行、账号,从配置读
BD_PAYEE_*)和「转账备注写申请单号」。 - 运营确认到账并开通:建专业版订阅、发放额度(流水「发放」,说明写申请单号),发站内通知;之后填发票号。
- 续费:当前专业版还没到期时,新一期从旧一期结束时开始;旧一期剩下的额度到期清零(「本期没用完的不结转」,原型)。
1.3 到期(FR-M6-05)
- worker 每天跑一次定时任务(Procrastinate 周期任务):到期的订阅改「已过期」、记「过期」流水把余额清零;排队的下一期到点发放。
- 提前 30 天、7 天、到期当天各发一次站内通知。
- 过期后:数据保留,只读(M6-5)。
1.4 删除项目(J4、G10、data-model §6.1)
- 入口:项目头「更多」菜单(和归档放一起),已递交、已归档的也能删。
- 确认框:写明删掉什么、保留什么(额度流水、不含业务内容的审计),输入项目名称确认(原型)。
DELETE /projects/{id}:同一事务里写审计、抹掉这个项目审计的内容、删项目行(级联)、销毁数据密钥;提交后后台任务删对象存储前缀。正在解析 / 检查的项目先取消任务再删(预扣退回)。
1.5 账号页与注销(J4、data-model §6.2)
- 「额度与套餐」页旁边一个「账号」页:企业名称、手机号(脱敏)、显示名、套餐;最下面「注销账号」。
- 注销:确认框写明后果(全部项目、资质库删除;开通申请和发票记录按财务要求保留;剩余额度作废、不退款),输入企业名称 + 短信验证码。
POST /tenant/close:立即吊销会话、企业标「已注销」,项目按 §1.4 由后台任务逐个删。
1.6 通知入口
- 顶栏加铃铛(原型有),有未读时带圆点;点开是浮层列表(最近 30 条:解析 / 检查 / 报告完成、额度提醒、开通结果、到期提醒),点一条跳到相关页面并标为已读,「全部标为已读」。
POST /notifications/read。
2. 运营后台(FR-M6-09)
2.1 形态
- 后端:同一个 Python 包的第三种进程
bid-doctor ops(和 api、worker 一样的镜像),单独端口、路径前缀/ops/v1,用bd_ops数据库角色(绕过行级安全策略);用户端 api 进程里不挂任何运营接口。部署时运营端口只对内网 / 白名单开放。 - 登录:用户名 + 密码 + TOTP 二次验证;IP 白名单(
BD_OPS_ALLOWED_IPS,空着时拒绝启动于生产环境);会话 8 小时;账号用命令行建(bid-doctor ops-admin create),没有自助注册。 - 前端:
apps/admin,Vue 3 + Vite,复用tokens.css和组件样式,不引入带样式的框架(M6-3);单独构建、单独部署,私有化不交付。 - 审计:每个运营操作(包括查询企业详情)都写审计事件,操作人记运营账号。
2.2 页面和接口(最小版)
| 页面 | 做什么 | 接口(/ops/v1) |
|---|---|---|
| 企业 | 按企业名称、手机号(用 HMAC 查)、申请单号搜;详情:成员(手机号脱敏)、套餐和到期日、各单位余额、项目数、最近任务;不显示项目名称和任何业务内容 | GET /tenants、GET /tenants/{id} |
| 开通申请 | 待处理在前;标记「已到账」→「开通」(选套餐和周期,默认专业版 1 年),或「退回」写原因;开通后填发票号 | GET /activation-requests、POST .../{id}/activate、/reject、/invoice |
| 额度调整 | 在企业详情里:单位、±数量、原因(必填) | POST /tenants/{id}/quota-adjustments |
| 反馈 | 误报 / 漏报列表:类型、用户写的理由、问题标题和级别、规则编号、提示词版本、出处页码,不显示招标 / 投标文件原文(M6-7) | GET /feedback |
| 失败任务 | 解析、检查、报告失败的:错误类别、错误码、阶段、耗时、模型调用次数,不含文档内容 | GET /failed-jobs |
| 审计 | 按企业 / 对象 / 时间查审计事件(项目删除后只剩类型和时间) | GET /audit-events |
3. 步骤
- 用户端后端:流水、提醒、开通申请、到期任务、删除项目、注销、通知已读,各配集成测试(额度对账:任何失败任务都没有净扣点;删除后对象不可解密)。
- 用户端前端:额度与套餐页、账号页、通知入口、删除项目。
- 运营后台后端:
ops进程、bd_ops角色(部署脚本)、登录和 TOTP、上表接口、审计。 - 运营后台前端:
apps/admin六个页面。 - 浏览器走查:注册 → 用完免费额度 → 申请开通 → 运营开通 → 流水核对 → 到期(改时钟)→ 删除项目 → 注销。
4. 待确认(建议见每条)
| # | 问题 | 建议 |
|---|---|---|
| M6-1 | 删除项目要不要「30 天回收站」(原型这么写) | 不要,立即彻底删除:按已确认的 G10、DM4 销毁数据密钥;回收站要软删除,和「彻底删除」冲突(同 QL6)。确认框要求输入项目名称,写明不能恢复。原型文案改掉 |
| M6-2 | 运营接口放在哪 | 单独的第三种进程 bid-doctor ops(同一镜像、单独端口、bd_ops 角色);用户端 api 进程里没有运营接口,绕过行级安全策略的代码不会被公网请求触到 |
| M6-3 | 运营后台前端选型 | Vue 3 + 复用 tokens.css / 组件样式,不引入带样式框架:页面只有六个、以表格和表单为主;和用户端同一套组件,维护成本低。另一种做法是引入 Element Plus 之类加快开发,但多一套视觉和依赖 |
| M6-4 | 运营登录 | 用户名 + 密码 + TOTP + IP 白名单,账号命令行创建(架构 §3.11 已写独立登录、白名单、二次验证) |
| M6-5 | 套餐到期后怎么办 | 不自动降为免费版;项目只读(能看、能下载已导出的报告、能删除;不能新建项目、解析、检查、改清单);额度清零;提前 30 天、7 天、当天提醒。续费到账后恢复 |
| M6-6 | 开通申请页写不写价格、收款信息从哪来 | 页面不写价格(D2 额度数字和定价都还没定),写「运营会联系你确认金额」;收款单位 / 开户行 / 账号从配置读,没配时显示「(待定)」,生产环境没配拒绝启动 |
| M6-7 | 运营看反馈时能不能看原文 | 不看原文:只看标题、理由、规则编号、提示词版本、页码(红线 10:数据敏感、按租户隔离)。要复盘原文时请用户导出报告发过来,或以后做「用户授权查看」 |
| M6-8 | 注销账号的确认方式 | 输入企业名称 + 短信验证码;有没到期的专业版时写明剩余额度作废、不退款 |
5. 落地记录(2026-11-05)
M6-1–M6-8 全部按建议确认后实现。和上文不一样、或计划里没写到的:
- 迁移 0010:订阅加「排队中」(scheduled),续费到账时当前一期还没到期就排队,到点由定时任务生效发放;开通申请加申请单号
request_no(KT- 加 6 位);通知加「已到期」「申请被退回」;billing_due(now)是 bd_auth 所有的 SECURITY DEFINER 函数,定时任务(worker 每小时billing_tick)用它跨企业找到期、该生效、该提醒的订阅,只拿编号,处理时回到各企业自己的租户事务。到期时还有没了结的预扣就等下一次再清零,避免退回的 1 次漏在余额里。 - 迁移 0011:
bd_ops的权限(跨企业只读、运营账号和会话表可写、审计可写),bd_app收回运营账号表的权限。角色本身由部署脚本建(init-roles.sh,CI 同一个脚本)。 - 只读的落点:到期后写操作在鉴权依赖里统一拦(
identity/deps.py的READ_ONLY_ALLOWED),退出、续费申请、删除项目、注销、通知已读、反馈、管理登录设备照常放行;不用逐个接口去改。 - 额度单位:只发放现在用得上的「解析」;专业版套餐里的
generate(M4 标书生成)等功能上线再发,免得顶栏出现用不了的额度。 - 删除项目:正在解析、检查、生成报告时返回 409
job_already_running,先取消(预扣退回)再删;data_key销毁以后不再建新密钥,还在跑的任务写不回文件。审计的before/after改为 Python 的 None 存成 SQL NULL(以前是 JSON 的 null)。 - 注销账号:验证码改为登录后
POST /me/sms-codes发到自己的号码(前端不知道完整号码);同步部分完成后手机号就能重新注册,删项目、资质库、抹审计内容、销毁密钥、清对象存储由后台任务close_tenant做,可以重复执行。 - uuid7 单调:同一进程内严格递增(RFC 9562 方法 2),额度流水按编号排序算逐条余额。
- 顺手修的:取消解析、取消检查的接口在刷新以前转 API 会触发懒加载报错(MissingGreenlet),取消按钮会 500。
- 运营后台:接口说明见
ops-api.md;前端apps/admin(pnpm dev:admin,:5174)六个页面:登录、开通申请、企业(含调额度)、误报 / 漏报、失败任务、审计。
测试:后端 156 个(新增 test_billing_flow.py 4 个、test_ops_console.py 3 个,覆盖流水对账、提醒只发一次、开通 / 续费 / 到期只读、删除后不可解密、注销后可重新注册、运营登录和 IP 白名单、bd_app 读不到运营表、bd_ops 改不了业务表)。浏览器走查:注册 → 申请开通 → 运营标记到账、开通、登记发票 → 流水核对 → 调额度 → 到期只读 → 删除项目 → 注销账号。
没做的:运营后台没有「撤回申请」「延长订阅」这类操作(需要时用退回 + 重新申请、调额度代替);收款信息和发票是线下流程,系统只登记发票号。
运营后台第二版(2026-11-05,决策 M6-9、M6-10):菜单改到左侧(墨色渐变底,标准横版 logo 反白,非生产环境挂赭色环境标签),分组可以展开收起、每项带图标和待办数(GET /summary),底部是当前运营人员(姓名、角色)和退出;登录页加 logo。运营人员的角色只留结构:迁移 0012 加 ops_admin.role,权限点和「角色 → 权限点」对应表、每个接口的检查点、按权限点过滤的菜单都已就位,现在只有管理员。见 ops-api.md。