befdd48c63
- Task3 TenantConfig 站点化(Site dataclass,消除与 Task5 的结构矛盾) - Task5 拆 5a(Registry 单元级)/5b(装配切换+conftest 迁移+默认租户过渡兜底) - conftest teardown dispose_all(Windows 文件锁) - 提取脚本改 AST 解析(不 import 源模块);REST session 收 ?site= - resolve=resolve_site 便捷形式注明 Co-Authored-By: Claude Code <noreply@anthropic.com>
333 lines
28 KiB
Markdown
333 lines
28 KiB
Markdown
# all-kefu 统一 AI 客服平台设计(spec)
|
||
|
||
- 日期:2026-10-12
|
||
- 状态:已与需求方逐节确认,待评审
|
||
- 来源项目:`D:\refactor\ai-kefu`(Hesu Auto 汽车出口客服,端口 8016)、`D:\refactor\ai-kefu-jc`(HESUPIPE 管道建材客服,端口 8017)
|
||
- 新项目位置:`D:\refactor\all-kefu`(本仓库)
|
||
|
||
## 1. 背景与问题
|
||
|
||
两个现有项目是**同一套 AI 客服引擎的两份拷贝**(2026-09-29 按当时的"方案 A:完整拷贝 + 行业化定制"分叉,约 85% 代码同源)。业务模式相同:AI 只做"需求了解 + 留资获客",价格/交期/MOQ 一律不正面回答、引导留联系方式,线索经邮件 + UniService CRM 推送转人工。技术栈相同:FastAPI + SQLite(aiosqlite) + 智谱 GLM(glm-4-flash)+ 纯静态前端(widget 用 script 标签 + Shadow DOM 嵌入官网)。
|
||
|
||
复制模式已产生的实际代价(均有实证):
|
||
|
||
1. **重复劳动**:2026-10-08 同一天,上游实现了 whatsapp-cc-completion,jc 独立实现了更完整的 cc-completion(判定树+状态机,376 行)——同一功能写了两遍且实现分叉。
|
||
2. **人肉同步**:上游修 bug 需对照 changelog 手动同步到 jc,规则写在 jc 的 CLAUDE.md 里。
|
||
3. **生产事故**:两套 docker-compose 在 1Panel 上项目名撞车(目录都叫 index),起一个容器会删掉另一个(互相 502)。已用 `name:` 修复,但这是"N 份部署"模式的固有风险。
|
||
4. **同页冲突**:两个 widget 源自同一代码,`sessionStorage` key(`widget_invite_dismissed_<path>`)与 host id(`cc-widget-host`)同名互扰;集团官网同页挂两个客服时必然出问题。
|
||
5. **僵尸配置**:ai-kefu 的 compose 跑着一个 Redis 容器,代码零引用。
|
||
|
||
每客户的差异只占约 15%(提示词、词表、品牌、环境配置、部署标识、少量代码点),其余 85% 是应该共享的引擎。**每加一个客户复制一套代码不可持续**——本设计把差异收敛为"租户配置",引擎单一份。
|
||
|
||
## 2. 目标与非目标
|
||
|
||
**目标**:
|
||
|
||
1. 一套代码(all-kefu)承载所有客户(租户),加客户 = 后台建租户 + 填配置 + 加一条反代,零代码复制、不起新容器。
|
||
2. 统一管理后台:超管管理所有租户、查看全局数据;租户管理员查看并配置自己租户的一切(含提示词与词表,需求方已确认"租户全开")。
|
||
3. 所有配置动态生效(热加载,不重启)。
|
||
4. 存量两个项目的数据与线上行为无感迁移(官网嵌入代码零改动、CRM 线索不重复)。
|
||
|
||
**非目标(明确推迟,YAGNI)**:
|
||
|
||
- 提示词沙箱试聊(用草稿配置开真实对话预览)——需要"草稿态"整套机制,二期;现阶段版本回滚 + 出厂重置够用。
|
||
- 换 PostgreSQL、超管/租户之外更细的角色权限、租户自助注册、计费——量级不到。
|
||
- 引擎行为重构(conversation.py 结构重写)——只做租户化依赖注入,不动结构。
|
||
- 推送失败自动告警——先靠仪表盘推送成功率卡片人工观察,二期视需要再加。
|
||
|
||
## 3. 已确认的关键决策
|
||
|
||
| 决策 | 结论 | 备注 |
|
||
|---|---|---|
|
||
| 运行架构 | **单进程多租户**,一个 FastAPI 进程 = 引擎 + 统一后台 | 需求方委托推荐后确认 |
|
||
| 数据隔离 | 每租户独立 SQLite 文件;平台数据(租户/账号/配置)在 platform.db | 文件级隔离,备份=拷文件 |
|
||
| 租户识别 | 按 Host 头(域名映射表);官网嵌入代码零改动 | 现有 widget 即从 script.src origin 推导连接地址 |
|
||
| 后台形态 | 统一后台,超管 + 租户管理员两级登录 | |
|
||
| 租户权限 | **全开**:租户可改自己租户的一切配置(含提示词、词表) | 配套安全网:版本历史/回滚/试金石/出厂重置 |
|
||
| 后台前端 | **art-design-pro**(Vue3 + TypeScript + Vite + Element Plus,MIT) | 替代原先"单文件原生 JS 后台"的设想 |
|
||
| 存量数据 | 导入新平台(写迁移脚本,保留原会话 ID) | |
|
||
| worker 数 | `--workers 1` 写死 | 内存 token/限流/配置缓存/每租户 engine 缓存均假设单进程 |
|
||
|
||
## 4. 总体架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ all-kefu 平台(单进程 FastAPI) │
|
||
chat.agent.hnxinyouhe.com ──┐ │
|
||
chat2.agent.hnxinyouhe.com ─┼→ 按 Host 识别: ├─ platform.db(租户/站点/域名/账号/配置)
|
||
ai.hesupipe.com(未来) ──┘ 域名→站点→租户 └─ tenants/auto.db、tenants/jc.db
|
||
多站共用域名时:挂件带 data-site 参数选站点
|
||
admin.xxx.com ────────────→ 统一后台(超管+租户登录)│
|
||
└─────────────────────────────────────────┘
|
||
```
|
||
|
||
- **一个进程**:同时承载所有租户的聊天引擎 + 统一管理后台,一个 Docker 容器部署在现有阿里云 ECS(1Panel)上。
|
||
- **租户识别 = Host,站点在中间**:数据库存"域名 → 站点 → 租户"映射。**站点(site)= 一个租户下的一张门面**:一个租户(一个 AI 脑子 + 一套线索管道)可以挂多个官网,每个官网的 brand(颜色/标题/欢迎语/话题/头像/WhatsApp/邮箱/隐私链接)可各配各的。站点接入两种方式:绑自己的域名(Host 自动识别),或多站共用一个挂件域名 + 嵌入代码 `data-site="es"` 参数选择;**未指定或未知 site 回退租户默认门面**(向后兼容)。页面 data-* 参数仍可覆盖站点门面(优先级:data-* > 站点 > 租户默认)。加租户 = 后台加记录 + DNS/反代指过来。
|
||
- **数据分层**:platform.db = 租户注册表、域名映射、admin_users、配置版本快照、平台级默认(集团 AI key、默认 SMTP);tenants/<tenant_id>.db = 现有 5 张表(conversations/messages/customer_profiles/evaluations/lead_push_logs),一租户一文件,互不可见。
|
||
- **两级登录**:超管(tenant_id 为空)看所有租户数据、增删租户、改任何配置;租户管理员只看自己租户数据、改自己配置。数据级隔离由后端强制。
|
||
|
||
**边界规则**:
|
||
|
||
- 未知 Host 一律 404,**不做兜底租户**(防 Host 混淆打错租户)。
|
||
- admin 后台走独立域名,不依赖 Host 租户解析。
|
||
- 停用租户(status=disabled):聊天/公开端点返回中性"服务暂停"页(widget 优雅降级);超管仍可只读其数据;Registry 释放其 DB engine。
|
||
|
||
## 5. 租户配置模型
|
||
|
||
### 5.1 配置 Schema(两个项目 15% 差异的收敛)
|
||
|
||
| 配置组 | 内容 | 对应现在散落的位置 |
|
||
|---|---|---|
|
||
| 基础 | 租户名、启用/停用、**站点列表**(每站点:短代号、名称、绑定域名、门面覆盖层) | compose/openresty 硬编码 |
|
||
| 品牌(租户默认门面,**站点可逐项覆盖**) | 展示名、主题色、头像、欢迎语、热门话题、输入框占位符、WhatsApp 号、联系邮箱、隐私政策 URL | chat.html / widget.js / chat.js 三处硬编码 |
|
||
| 提示词 4 件套 | system.md、company_facts.md(知识库)、extractor.md、evaluator.md(markdown) | app/prompts/ |
|
||
| 安全墙词表 | 价格墙正则组(多语言)、迂回话术池(EN/ZH/ES)、零号码替换句、触发词正则(边界/预算/索要)、**轮中提醒话术**(边界/索要/语言跟随/已留资/CC 指引/邮箱轻补六类) | boundary_filter.py / conversation.py 内嵌 |
|
||
| 联系方式策略 | 区号补全开关、模板句语言集、轻补邮箱开关 | conversation.py 内嵌参数 |
|
||
| 邮件 | SMTP(可选"用平台默认")、发件人显示名、通知邮箱列表 | .env + email_service.py 硬编码 |
|
||
| 推送 | 启用开关、URL、API Key、**external_key 前缀(全局唯一约束)**、超时/重试 | .env |
|
||
| AI 凭据 | 可选覆盖(不填=平台默认集团 key) | .env |
|
||
| 引擎杂项 | context 种子键映射(product_name vs vehicle_name)、限流参数、国名映射扩展 | chat_rest.py / cc_completion.py 代码差异 |
|
||
|
||
### 5.2 存储(platform.db)
|
||
|
||
```sql
|
||
tenants(id, slug, name, status, created_at)
|
||
sites(id, tenant_id, slug, name, status, brand_overlay JSON) -- 站点门面覆盖层(缺省项继承租户 brand)
|
||
site_domains(domain UNIQUE, site_id) -- 域名→站点(站点→租户随外键)
|
||
tenant_config_versions(tenant_id, version, config_json, updated_by, comment, created_at)
|
||
-- 当前生效版 = MAX(version);每次保存存整份快照(几十 KB,不做增量)
|
||
admin_users(id, tenant_id NULL=超管, username, password_hash, status)
|
||
platform_settings(key, value) -- 平台级默认:集团 AI 凭据、默认 SMTP
|
||
```
|
||
|
||
回滚 = 把旧 version 复制成新 version(历史不可变)。提示词与词表在同一快照内,回滚原子。
|
||
|
||
### 5.3 生效机制(热加载)
|
||
|
||
进程内 TenantRegistry:`tenant_id → { config, db_engine, session_factory, manager }`。后台保存 → 校验 → 写 platform.db → **原子替换 Registry 条目**。进行中的会话下一条消息即用新配置(提示词逐轮拼装,天然支持)。不重启、无分发协议——单进程架构红利。并发编辑策略:同一租户被两人同时保存时后保存者覆盖(last-write-wins)——单人维护场景接受,不做乐观锁。
|
||
|
||
### 5.4 保存校验 + 试金石(租户可编辑正则的安全网)
|
||
|
||
1. **编译校验**:每条正则 re.compile 试跑,非法整单拒收。
|
||
2. **试金石面板**:编辑页内嵌测试框,贴样例句("What's your price for DN800?"、"预算5000美元")实时显示拦/放/替换判定,发布前眼见为实。
|
||
3. **回滚**:版本列表任意旧版一键恢复;每个配置组另有"重置为出厂默认"。
|
||
|
||
### 5.5 密钥处理
|
||
|
||
- 后台读取一律掩码;仅主动修改时明文输入;"用平台默认"状态下租户侧不存密钥。
|
||
- platform.db 备份按敏感件对待(含 SMTP/push/AI 密钥),写入部署文档。
|
||
|
||
### 5.6 关键设计规则
|
||
|
||
- **话术单一事实源(R1)**:迂回话术池只存于墙配置;ai_service 组装 system prompt 时以模板变量(`$DEFLECTION_POOL` 等)注入提示词。改一处,提示词与墙同时变——消除"提示词教的说法"与"墙的执法标准"分叉。
|
||
- **配置演进(E4)**:Registry 加载 = 快照深合并出厂默认值,引擎升级新增字段不炸旧租户。
|
||
- **external_key 前缀全局唯一(E1)**:数据库约束 + 保存校验,防 CRM 线索空间串号。
|
||
- **tenant slug 创建后不可变**:客户端存储 key 全部以 slug 命名,改动 slug = 重置所有客户页面的记忆(邀请卡状态、会话令牌)。site slug 同理不可变。
|
||
- **站点只覆盖门面**:brand 跟站点走;AI 脑子(提示词/词表/行为开关/AI 凭据)与线索管道(邮件/推送)留租户级——一个租户 = 一个客户经理团队收所有站点的线索,脑子分叉就乱。需要"两套完全不同的 AI"时应建两个租户而非两个站点。会话记录带站点标(后台可按官网筛线索、看各站转化)。
|
||
- **出厂档案**:`seed_profiles/auto/`、`seed_profiles/jiancai/`(提示词 md + 词表 json + 品牌 json),由提取脚本从两个老项目生成;新租户创建时选出厂档案或空白。两个概念分清:**重置出厂** = 代码库当前模板(随平台升级演进);**租户创建** = 当时选定档案的快照(不随升级变化)。
|
||
|
||
### 5.7 内容与文件上限
|
||
|
||
- 头像等上传文件存**挂载卷** `data/uploads/`:服务端 uuid 重命名(防路径穿越)+ 类型校验 + 大小 ≤2MB,经接口动态读取——绝不写入镜像静态目录(容器重建即丢,同类教训:改 static 必须 build)。头像按 (租户[, 站点]) 维度解析:站点覆盖 > 租户默认 > 平台兜底图。
|
||
- 提示词/知识库保存时校验大小:知识库软上限 50KB,超限拒收并提示精炼——company_facts 每轮整段拼入 system prompt,过大会线性放大每轮 LLM 成本与时延,租户自己无感。
|
||
|
||
## 6. 引擎改造与代码合并
|
||
|
||
### 6.1 TenantContext——重构主轴
|
||
|
||
现状服务层是"进程级全局"(import 时读提示词、全局 Settings、模块级正则、全局 engine)。改造为"随租户走":
|
||
|
||
```python
|
||
@dataclass
|
||
class TenantRuntime:
|
||
tenant_id: str
|
||
config: TenantConfig # 快照 ⊕ 出厂默认 深合并;词表已编译校验
|
||
engine: AsyncEngine # 该租户 SQLite(懒创建,停用时关闭)
|
||
session_factory: async_sessionmaker
|
||
manager: ConversationManager
|
||
|
||
class TenantRegistry:
|
||
resolve(host) -> TenantRuntime # 域名表缓存,后台改域名即失效
|
||
reload(tenant_id) # 配置保存后原子换条目
|
||
```
|
||
|
||
请求流:中间件按 Host 解析租户 → `request.state` → 路由把 runtime 传进服务。服务层从"读全局"变"接参数"。
|
||
|
||
逐文件改造点:
|
||
|
||
| 文件 | 现状 | 改造后 |
|
||
|---|---|---|
|
||
| config.py | 全局 Settings 读 .env | 拆 ProcessSettings(.env:平台库路径/端口/超管初始密码)+ TenantConfig(platform.db);顺带丢弃 Redis 死配置(REDIS_URL/REDIS_PASSWORD) |
|
||
| ai_service.py | import 时读 4 提示词 | PromptProvider 挂 TenantRuntime;组装 = system.md + company_facts.md + `$DEFLECTION_POOL` 注入 + 当轮 context reminder |
|
||
| conversation.py(约 1250 行) | 3 组模块级正则 + 逐字话术 | 正则/话术从 config.wall 编译;Manager 每租户实例化;**只做依赖注入,不重写结构** |
|
||
| boundary_filter.py | 正则话术焊死 | 纯函数化:接收已编译词表,机制与数据分离 |
|
||
| email/lead_push | .env 全局配置 | 读租户配置段;启动 sweep_stale_pending 逐租户跑 |
|
||
| rate_limit.py | key=ip+device | key=(tenant_id, ip, device) |
|
||
| admin 路由 | 内存 token + 单密码 | platform.db 认证 + 会话超时 + 登录失败限流 |
|
||
|
||
表外三条硬纪律:
|
||
|
||
- **每租户库打开即 `PRAGMA journal_mode=WAL`**(读写不互斥,优于现在靠 timeout=30s 硬扛);platform.db 同样开启。
|
||
- 每租户 engine 连接池收紧至 1-2(SQLite 单文件用不上默认 5 连接;20 租户 × 5 = 100 个句柄纯属浪费)。
|
||
- **事件循环零同步 IO**:智谱 SDK 同步流式必须走线程 executor(沿用上游 glm.py 做法);推送 HTTP 改用 httpx.AsyncClient(后台重试的几秒不能卡住全平台);SMTP 已是 aiosmtplib。合并时逐点审计。
|
||
|
||
### 6.2 合并清单(jc 为主线)
|
||
|
||
**取自 jc**(10-08 两条分叉线中更完整一方):
|
||
|
||
- cc_completion.py 完整判定树 + 状态机 + extractor cc_reply + e164 出站渲染
|
||
- 多语言墙(€/£、西/葡/俄/阿词表、ES 迂回池、建材防误杀白名单)
|
||
- dual-contact 分步双要(先 WhatsApp 带国家码→轻补邮箱)+ `_email_asked`
|
||
- CC 强制墙 + 零号码墙回显豁免收窄
|
||
- XFF 服务端优先、阿语数字归一、resolve_region_code
|
||
|
||
**从上游吸收**:input-placeholder 可配置化(落进品牌配置组);上游独有测试的断言价值(test_channel_hygiene、test_chat_rest_ip、test_lead_push_cc、test_cc_integration、test_ip_service_region、test_prompt_static 等)。
|
||
|
||
**不带走**:blog.html(模板残留)、Redis 配置(死代码)、上游旧版 whatsapp-cc-completion(被 jc 版取代,但其测试断言移植过来验证 jc 版行为)。
|
||
|
||
### 6.3 前端
|
||
|
||
1. widget.js:启动时 `fetch /api/public/brand`(Host 已识别租户,免鉴权返回品牌 JSON,**响应携带 tenant slug**;script 标签有 `data-site` 时带 `?site=` 选站点)→ 与 data-* 合并(data-* 优先);失败时 data-*/中性默认兜底;no-cache。
|
||
2. 客户端 key 全部租户化(`<tenant>` 取 brand 响应里的 tenant slug):`cc-widget-host-<tenant>`、`widget_invite_dismissed_<tenant>_<path>`、chat 页 localStorage 会话 key 同理——同页双挂件根治。
|
||
3. widget.js 与 chat.html 保持零构建原生 JS(嵌入官网资产必须轻量零依赖);主题派生单元机制不变,默认值来自品牌配置。
|
||
4. WS 通道补限流:现有限流只覆盖 REST 消息接口;平台化后单 IP 可对所有租户域名各开一圈 WS 耗尽单进程资源——加每 IP 并发 WS 连接上限(如 10,内存计数,超限拒绝)。
|
||
|
||
### 6.4 目标目录结构
|
||
|
||
```
|
||
all-kefu/
|
||
admin/ # art-design-pro SPA(Vue3+Vite+TS+Element Plus)
|
||
app/
|
||
main.py # 平台入口:租户中间件 + 路由挂载,--workers 1
|
||
platform/ # registry、配置服务、校验+试金石、admin 认证、platform.db 模型
|
||
engine/ # 合并后的客服引擎(jc+上游),全部租户感知
|
||
routes/ # ws/chat、rest/chat(租户作用域)
|
||
services/ # conversation、boundary_filter、cc_completion、contact_pipeline、
|
||
# email、lead_push、ip、rate_limit、ai_service(PromptProvider)
|
||
providers/ # glm
|
||
models/ # 租户库 5 张表 + 逐租户 _migrate
|
||
static/ # widget.js、chat.html、theme(零构建)
|
||
seed_profiles/ # auto/、jiancai/
|
||
tenants/ # 运行期:每租户一个 .db
|
||
platform.db
|
||
scripts/ # extract_config.py、import_legacy.py、mock_uniservice
|
||
tests/
|
||
docs/
|
||
```
|
||
|
||
`platform/` 与 `engine/` 是隔离边界:引擎不感知平台存在,只认 TenantRuntime——将来某租户需独立部署时 engine 包可整体拎走。
|
||
|
||
运行期产物**绝不进 git**(首次提交前建好 .gitignore):`tenants/*.db`、`platform.db`(含密钥,进了 git 历史就洗不干净)、`data/uploads/`、`admin/node_modules/`、`admin/dist/`。
|
||
|
||
## 7. 统一管理后台(art-design-pro)
|
||
|
||
### 7.1 工程与构建
|
||
|
||
- `admin/` 独立 SPA;开发时 vite dev proxy 到 FastAPI;生产两阶段 Docker(node 阶段 pnpm build → 产物拷入 Python 镜像),服务器零 node 依赖。
|
||
- admin 静态文件打进镜像(延续"改前端必须 build"的既有教训);由 FastAPI 在管理域名下同源服务,无 CORS。
|
||
- 只有 admin 走前端工程化;widget/chat 保持原生 JS。
|
||
- admin SPA 用 history 路由;FastAPI 仅在**管理域名**下做 index.html 回退,不影响租户域名路径。
|
||
|
||
### 7.2 认证与权限
|
||
|
||
- admin_users 表(超管 tenant_id 为空);token + 会话超时 + 登录失败限流 + 超管强制改初始密码。
|
||
- art-design-pro 路由守卫/权限菜单映射两级角色:菜单级权限(超管可见租户管理/平台配置)+ 后端强制的数据级隔离(租户查询自动 scope)。
|
||
- admin_users 密码用 bcrypt/argon2 哈希;SPA token 走 Authorization 头而非 Cookie(规避 CSRF 整类问题)。
|
||
|
||
### 7.3 页面清单
|
||
|
||
| 页面 | 超管 | 租户管理员 |
|
||
|---|---|---|
|
||
| 仪表盘 | 租户卡片墙(今日会话/线索/推送成功率,**逐库查询 + 应用层合并**——不用 SQLite ATTACH,其默认上限 10 个附加库,租户一多就炸)+ 平台总览 | 只看自己指标 |
|
||
| 租户管理 | 创建向导(名称→域名→选出厂档案 auto/jiancai/空白→生成租户管理员)、停用/启用、域名绑定 | ✗ |
|
||
| 会话与线索 | 顶部租户切换器,看所有租户会话/画像/推送日志 | 只看自己的 |
|
||
| 配置中心 | 编辑任何租户全部配置组 | 编辑自己全部配置组(全开) |
|
||
| 站点管理 | 租户下站点增删/停用、域名绑定、门面编辑(覆盖层+继承项提示+嵌入代码含 data-site) | 同左(限自己租户) |
|
||
| 统计分析 | 全租户对比 + 单租户下钻 | 只看自己租户 |
|
||
| 版本历史 | 快照列表、两版对比、一键回滚(回滚生成新版本) | 同 |
|
||
| 平台配置 | 集团默认 AI 凭据、默认 SMTP、超管改密 | ✗ |
|
||
|
||
### 7.4 配置编辑形态
|
||
|
||
- 品牌:表单(颜色选择器、头像上传、文案)+ 挂件实时预览 + **嵌入代码复制块**(显示本租户 `<script>` 标签与域名,复制即用,替代人肉查嵌入文档)。
|
||
- 提示词:markdown 编辑器(语法高亮),侧栏显示 `$DEFLECTION_POOL` 等注入变量当前值,保存必须留 comment;预览用安全渲染(sanitize,防 markdown 内嵌 HTML 注入)。
|
||
- 词表:结构化表格(每条正则一行:语言组/表达式/备注)+ 编译校验 + 内嵌试金石。
|
||
- 邮件:SMTP 表单 + 发测试邮件;推送:目标配置 + 连通性探测(只验鉴权,不发真数据)+ 前缀唯一校验。
|
||
|
||
### 7.5 API 契约
|
||
|
||
后台 SPA 走 `/api/platform/*`(新增)与租户化 admin 数据端点;聊天引擎端点(`/ws/chat`、`/api/chat/*`、`/api/public/brand`)契约不变——widget 嵌入方完全无感。
|
||
|
||
`/api/public/brand` 用**显式字段白名单**序列化(只吐品牌字段,绝不序列化含密钥的整个配置 JSON);支持 `?site=<slug>` 返回"站点覆盖层 ⊕ 租户默认门面"的合并结果,未带或未知 site 回退租户默认;该端点被客户官网跨域 fetch,响应需带 `Access-Control-Allow-Origin: *`(与 widget.js 现行 CORS 策略一致)。
|
||
|
||
### 7.6 统计分析页(数据全部来自现有五张表,无新表)
|
||
|
||
仪表盘是速览卡片,统计页是下钻分析。内容:
|
||
|
||
- **核心卡片**:会话数、线索数(已留联系方式的会话)、高意向数、推送成功率
|
||
- **转化漏斗**:会话 → 留资 → 完成评估 → 推送成功(每级流失可见)
|
||
- **趋势图**:按日会话量/线索量(近 7/30/90 天 + 自定义区间)
|
||
- **分布**:意向三级占比、来源(widget/app)、**站点(哪个官网带来的量)**、国家 Top10(IP 归国)
|
||
- **推送健康**:success / failed / skipped / duplicated 计数
|
||
- **筛选**:时间范围 + 站点;超管可跨租户对比与单租户下钻
|
||
|
||
实现:`/api/platform/stats` 聚合端点(**逐库 SQL 聚合 + 应用层合并**,沿用"不用 ATTACH"的决定);前端用 ECharts(Element Plus 生态标配)。统计 API 归 Phase 2 平台层、页面归 Phase 3 后台——写那两份计划时据此展开任务。
|
||
|
||
二期不做:墙拦截率统计(需在消息层给墙命中做埋点,现阶段继续用 `scripts/breach_rate.py` 离线观察)。
|
||
|
||
## 8. 数据迁移
|
||
|
||
**scripts/extract_config.py**:从两个老项目提取首版配置快照,生成 seed_profiles/auto/ 与 seed_profiles/jiancai/(提示词拷 md;词表从 boundary_filter.py/conversation.py 提取正则话术转 JSON;品牌从 chat.html/widget.js/chat.js 三处硬编码 + .env 归一)。此后两个老项目只读。
|
||
|
||
**scripts/import_legacy.py**:老库 5 张表原结构导入对应租户库;**保留原 conversation_id**(external_key 连续,UniService 不产生重复线索);session_id/page_token/status 等字段全部原样保留(切换时刻客户凭页面令牌无缝续聊);老库 status=pending 的推送日志导入即标 failed 并加注释(切换前未完成态,新平台不重试);导入后对账报告(两会话数/消息数/推送日志数 + 最近 10 条会话抽查)。脚本幂等:按目标库已有数据跳过,重复执行不产生重复行。
|
||
|
||
**切换顺序**:新平台部署验证(不切域名)→ 导存量 → 租户域名反代从老容器切到新平台 → 观察一周(沿用 breach_rate 双向偏差观察法)→ 老项目归档下线。
|
||
|
||
## 9. 部署
|
||
|
||
```
|
||
chat.agent.hnxinyouhe.com ─┐
|
||
chat2.agent.hnxinyouhe.com ─┼→ 1Panel 各站点反代 → 127.0.0.1:8018 → all-kefu 容器
|
||
admin.xxx.com(新,具体值部署前由需求方确定)─┘
|
||
```
|
||
|
||
- 一个容器一个 upstream;compose 固定 `name: all-kefu`(互删根因根治——只剩一个 compose 项目);WS 升级头单套(426 教训);`/static/widget.js` no-cache(缓存教训);`--workers 1`;容器 `TZ=Asia/Shanghai`(仪表盘"今日"边界与业务时区一致);mem_limit 768m(单进程承载 admin SPA 服务 + 全部租户引擎,较现在单租户 512m 上调)。
|
||
- 日志:compose `logging` 设 json-file + `max-size: 10m` + `max-file: 3` 封顶(Docker 默认不限大小,会涨满磁盘);应用日志只进 stdout,且**不打聊天内容/联系方式等 PII**(库内数据有 admin 权限保护,进日志等于裸奔磁盘)。
|
||
- 备份:platform.db(敏感件)+ tenants/*.db 用 SQLite backup API(或 VACUUM INTO)做一致性快照,每日执行;**禁止运行时直接拷 .db 文件**(可能拷到写一半状态)。每季度做一次恢复演练(没验证过的备份等于没有备份)。写入部署文档。
|
||
- 数据保留:lead_push_logs.request_payload 存完整对话 JSON、只涨不掉;提供 scripts/archive.py(会话超 12 个月导出归档 + 清理 + VACUUM 回收空间),后台手动触发(不做自动任务,YAGNI)。
|
||
- `/health` 分级:进程状态 + 各租户 engine 状态 + 域名表版本;单租户库异常时报告"哪个租户不健康",不整体 500(防 1Panel 误判容器死亡反复重启)。
|
||
- 发版回滚:镜像打版本 tag,compose 可 30 秒切回上一版,流程写入部署文档;部署文档整体继承 jc 项目 deploy-production.md 踩坑手册(1Panel include 行、面板位置联动、no-cache 等)。
|
||
- 加租户 = 后台建租户 + 1Panel 加一条反代,不起容器。
|
||
|
||
## 10. 测试策略
|
||
|
||
1. **机制测试全保留**:jc 的全部机制测试(约 320 个用例,实测计数;jc 文档旧称 230 项)+ 上游独有测试,移植到统一 conftest——租户夹具(临时 platform.db + auto/jc 两个种子租户 + 各自租户库),断言基本不动;LLM 全 mock 原则不变。
|
||
2. **多租户专项(改造的回归安全网)**:Host 路由(A 域名进 A、未知 Host 404);跨租户数据隔离(A 的 admin/API 摸不到 B 数据);配置热切换(保存新提示词→下一条消息生效;改词表→墙行为即时变);前缀唯一约束;快照深合并默认值;试金石判定;widget 客户端 key 租户前缀(静态断言);停用租户行为。
|
||
3. **真实 LLM 验收**:真实智谱 Key + scripts/mock_uniservice.py 联调流程照旧(沿用 test-checklist 体系)。
|
||
|
||
## 11. 风险与对策
|
||
|
||
| 风险 | 对策 |
|
||
|---|---|
|
||
| conversation.py(1250 行核心编排器)租户化回归 | 230 项机制测试安全网 + 只做依赖注入不重写 |
|
||
| admin 引入 node 构建链 | 两阶段 Docker 隔离,服务器零 node;widget/chat 零构建 |
|
||
| 租户改坏提示词/词表 | 版本回滚 + 出厂重置 + 编译校验 + 试金石;话术池单一事实源消除双改遗漏 |
|
||
| 迁移导致 CRM 重复线索 | 保留原 conversation_id + external_key 前缀不变 |
|
||
| 单进程升级影响全部租户 | 秒级重启可接受;engine/ 与 platform/ 隔离保留拆分路径 |
|
||
| 密钥入库 | 掩码显示、platform.db 按敏感件备份 |
|
||
| 后台暴露面变大 | 登录失败限流、超管强制改初始密码、会话超时、admin 独立域名 |
|
||
| 事件循环被同步 IO 阻塞(单进程生死线:一个租户的慢调用卡住所有租户) | 零同步 IO 纪律:智谱流式走 executor、推送 HTTP 异步化、SMTP 已异步;合并时逐点审计 |
|
||
|
||
## 12. 附录:两项目差异实证(配置 Schema 的来源)
|
||
|
||
| 差异点 | ai-kefu(auto) | ai-kefu-jc(建材) |
|
||
|---|---|---|
|
||
| 提示词 4 件套 | 汽车语义 | 管道建材语义(多语言墙、规格防误杀) |
|
||
| 端口 / DB / 容器 | 8016 / cc_ai_kefu.db / cc-ai-kefu | 8017 / hesupipe_ai_kefu.db / hs-ai-kefu |
|
||
| 品牌色 / 发件人 | 橙 #f08a31 / 【XYH】 | 蓝 #066fc0 / 【HESUPIPE】 |
|
||
| context 种子键 | vehicle_name/vehicle_id | product_name/product_id |
|
||
| 推送键前缀 | ai-kefu | ai-kefu-jc |
|
||
| cc 实现 | whatsapp-cc-completion(旧) | cc_completion 判定树(新,主线) |
|
||
| 独有功能 | input-placeholder 配置、blog.html(弃) | dual-contact、CC 强制墙、阿语数字归一 |
|
||
| Redis | 有容器但代码零引用(弃) | 分叉时已移除 |
|