docs: 站点模型(一租户多官网多门面)+ 6 项缺口修正落档 spec 与 Phase 1 计划
- spec:域名→站点→租户解析链、sites/site_domains 表、站点管理页、brand 接口 ?site=、 词表补轮中提醒话术、头像按(租户,站点)解析 - 计划:Task4 加 extract_config.py 前置与 reminders 组、Task5 tenant.json 站点化、 Task7 中间件带站点、Task9 扩为七类接线(AI凭据/行为开关/映射表/种子键)、 Task10 站点覆盖下发+每租户头像资产+data-site+会话站点标 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -54,14 +54,15 @@
|
||||
┌─────────────────────────────────────────┐
|
||||
│ all-kefu 平台(单进程 FastAPI) │
|
||||
chat.agent.hnxinyouhe.com ──┐ │
|
||||
chat2.agent.hnxinyouhe.com ─┼→ 按 Host 识别租户 → ├─ platform.db(租户/域名/账号/配置)
|
||||
ai.hesupipe.com(未来) ──┘ (域名映射表) └─ tenants/auto.db、tenants/jc.db
|
||||
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**:数据库存"域名 → 租户"映射;加租户 = 后台加记录 + DNS/反代指过来。
|
||||
- **租户识别 = 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 为空)看所有租户数据、增删租户、改任何配置;租户管理员只看自己租户数据、改自己配置。数据级隔离由后端强制。
|
||||
|
||||
@@ -77,10 +78,10 @@
|
||||
|
||||
| 配置组 | 内容 | 对应现在散落的位置 |
|
||||
|---|---|---|
|
||||
| 基础 | 租户名、启用/停用、绑定域名列表 | compose/openresty 硬编码 |
|
||||
| 品牌 | 展示名、主题色、头像、欢迎语、热门话题、输入框占位符、WhatsApp 号、联系邮箱、隐私政策 URL | chat.html / widget.js / chat.js 三处硬编码 |
|
||||
| 基础 | 租户名、启用/停用、**站点列表**(每站点:短代号、名称、绑定域名、门面覆盖层) | 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)、零号码替换句、触发词正则(边界/预算/索要)、防误杀白名单(建材 DN/PN/SDR vs 汽车 km/年款) | boundary_filter.py / conversation.py 内嵌 |
|
||||
| 安全墙词表 | 价格墙正则组(多语言)、迂回话术池(EN/ZH/ES)、零号码替换句、触发词正则(边界/预算/索要)、**轮中提醒话术**(边界/索要/语言跟随/已留资/CC 指引/邮箱轻补六类) | boundary_filter.py / conversation.py 内嵌 |
|
||||
| 联系方式策略 | 区号补全开关、模板句语言集、轻补邮箱开关 | conversation.py 内嵌参数 |
|
||||
| 邮件 | SMTP(可选"用平台默认")、发件人显示名、通知邮箱列表 | .env + email_service.py 硬编码 |
|
||||
| 推送 | 启用开关、URL、API Key、**external_key 前缀(全局唯一约束)**、超时/重试 | .env |
|
||||
@@ -91,7 +92,8 @@
|
||||
|
||||
```sql
|
||||
tenants(id, slug, name, status, created_at)
|
||||
tenant_domains(domain UNIQUE, tenant_id)
|
||||
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)
|
||||
@@ -120,12 +122,13 @@ platform_settings(key, value) -- 平台级默认:集团 AI 凭据、默认 S
|
||||
- **话术单一事实源(R1)**:迂回话术池只存于墙配置;ai_service 组装 system prompt 时以模板变量(`$DEFLECTION_POOL` 等)注入提示词。改一处,提示词与墙同时变——消除"提示词教的说法"与"墙的执法标准"分叉。
|
||||
- **配置演进(E4)**:Registry 加载 = 快照深合并出厂默认值,引擎升级新增字段不炸旧租户。
|
||||
- **external_key 前缀全局唯一(E1)**:数据库约束 + 保存校验,防 CRM 线索空间串号。
|
||||
- **tenant slug 创建后不可变**:客户端存储 key 全部以 slug 命名,改动 slug = 重置所有客户页面的记忆(邀请卡状态、会话令牌)。
|
||||
- **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)。
|
||||
- 头像等上传文件存**挂载卷** `data/uploads/`:服务端 uuid 重命名(防路径穿越)+ 类型校验 + 大小 ≤2MB,经接口动态读取——绝不写入镜像静态目录(容器重建即丢,同类教训:改 static 必须 build)。头像按 (租户[, 站点]) 维度解析:站点覆盖 > 租户默认 > 平台兜底图。
|
||||
- 提示词/知识库保存时校验大小:知识库软上限 50KB,超限拒收并提示精炼——company_facts 每轮整段拼入 system prompt,过大会线性放大每轮 LLM 成本与时延,租户自己无感。
|
||||
|
||||
## 6. 引擎改造与代码合并
|
||||
@@ -184,7 +187,7 @@ class TenantRegistry:
|
||||
|
||||
### 6.3 前端
|
||||
|
||||
1. widget.js:启动时 `fetch /api/public/brand`(Host 已识别租户,免鉴权返回品牌 JSON,**响应携带 tenant slug**)→ 与 data-* 合并(data-* 优先);失败时 data-*/中性默认兜底;no-cache。
|
||||
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,内存计数,超限拒绝)。
|
||||
@@ -239,6 +242,7 @@ all-kefu/
|
||||
| 租户管理 | 创建向导(名称→域名→选出厂档案 auto/jiancai/空白→生成租户管理员)、停用/启用、域名绑定 | ✗ |
|
||||
| 会话与线索 | 顶部租户切换器,看所有租户会话/画像/推送日志 | 只看自己的 |
|
||||
| 配置中心 | 编辑任何租户全部配置组 | 编辑自己全部配置组(全开) |
|
||||
| 站点管理 | 租户下站点增删/停用、域名绑定、门面编辑(覆盖层+继承项提示+嵌入代码含 data-site) | 同左(限自己租户) |
|
||||
| 版本历史 | 快照列表、两版对比、一键回滚(回滚生成新版本) | 同 |
|
||||
| 平台配置 | 集团默认 AI 凭据、默认 SMTP、超管改密 | ✗ |
|
||||
|
||||
@@ -253,7 +257,7 @@ all-kefu/
|
||||
|
||||
后台 SPA 走 `/api/platform/*`(新增)与租户化 admin 数据端点;聊天引擎端点(`/ws/chat`、`/api/chat/*`、`/api/public/brand`)契约不变——widget 嵌入方完全无感。
|
||||
|
||||
`/api/public/brand` 用**显式字段白名单**序列化(只吐品牌字段,绝不序列化含密钥的整个配置 JSON);该端点被客户官网跨域 fetch,响应需带 `Access-Control-Allow-Origin: *`(与 widget.js 现行 CORS 策略一致)。
|
||||
`/api/public/brand` 用**显式字段白名单**序列化(只吐品牌字段,绝不序列化含密钥的整个配置 JSON);支持 `?site=<slug>` 返回"站点覆盖层 ⊕ 租户默认门面"的合并结果,未带或未知 site 回退租户默认;该端点被客户官网跨域 fetch,响应需带 `Access-Control-Allow-Origin: *`(与 widget.js 现行 CORS 策略一致)。
|
||||
|
||||
## 8. 数据迁移
|
||||
|
||||
|
||||
Reference in New Issue
Block a user