docs: spec 定稿补丁(ATTACH 改逐库合并/gitignore/导入幂等/slug 不可变/last-write-wins/嵌入代码块/出厂语义/恢复演练/mem_limit/md sanitize/部署文档继承)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-12 14:52:19 +08:00
parent 163bd3900a
commit d1c10f3281
@@ -102,7 +102,7 @@ platform_settings(key, value) -- 平台级默认:集团 AI 凭据、默认 S
### 5.3 生效机制(热加载)
进程内 TenantRegistry:`tenant_id → { config, db_engine, session_factory, manager }`。后台保存 → 校验 → 写 platform.db → **原子替换 Registry 条目**。进行中的会话下一条消息即用新配置(提示词逐轮拼装,天然支持)。不重启、无分发协议——单进程架构红利。
进程内 TenantRegistry:`tenant_id → { config, db_engine, session_factory, manager }`。后台保存 → 校验 → 写 platform.db → **原子替换 Registry 条目**。进行中的会话下一条消息即用新配置(提示词逐轮拼装,天然支持)。不重启、无分发协议——单进程架构红利。并发编辑策略:同一租户被两人同时保存时后保存者覆盖(last-write-wins)——单人维护场景接受,不做乐观锁。
### 5.4 保存校验 + 试金石(租户可编辑正则的安全网)
@@ -120,7 +120,8 @@ platform_settings(key, value) -- 平台级默认:集团 AI 凭据、默认 S
- **话术单一事实源(R1)**:迂回话术池只存于墙配置;ai_service 组装 system prompt 时以模板变量(`$DEFLECTION_POOL` 等)注入提示词。改一处,提示词与墙同时变——消除"提示词教的说法"与"墙的执法标准"分叉。
- **配置演进(E4)**:Registry 加载 = 快照深合并出厂默认值,引擎升级新增字段不炸旧租户。
- **external_key 前缀全局唯一(E1)**:数据库约束 + 保存校验,防 CRM 线索空间串号。
- **出厂档案**:`seed_profiles/auto/`、`seed_profiles/jiancai/`(提示词 md + 词表 json + 品牌 json),由提取脚本从两个老项目生成;新租户创建时选出厂档案或空白。
- **tenant slug 创建后不可变**:客户端存储 key 全部以 slug 命名,改动 slug = 重置所有客户页面的记忆(邀请卡状态、会话令牌)。
- **出厂档案**:`seed_profiles/auto/`、`seed_profiles/jiancai/`(提示词 md + 词表 json + 品牌 json),由提取脚本从两个老项目生成;新租户创建时选出厂档案或空白。两个概念分清:**重置出厂** = 代码库当前模板(随平台升级演进);**租户创建** = 当时选定档案的快照(不随升级变化)。
### 5.7 内容与文件上限
@@ -213,6 +214,8 @@ all-kefu/
`platform/` 与 `engine/` 是隔离边界:引擎不感知平台存在,只认 TenantRuntime——将来某租户需独立部署时 engine 包可整体拎走。
运行期产物**绝不进 git**(首次提交前建好 .gitignore):`tenants/*.db`、`platform.db`(含密钥,进了 git 历史就洗不干净)、`data/uploads/`、`admin/node_modules/`、`admin/dist/`。
## 7. 统一管理后台(art-design-pro)
### 7.1 工程与构建
@@ -232,7 +235,7 @@ all-kefu/
| 页面 | 超管 | 租户管理员 |
|---|---|---|
| 仪表盘 | 租户卡片墙(今日会话/线索/推送成功率,ATTACH 跨库聚合)+ 平台总览 | 只看自己指标 |
| 仪表盘 | 租户卡片墙(今日会话/线索/推送成功率,**逐库查询 + 应用层合并**——不用 SQLite ATTACH,其默认上限 10 个附加库,租户一多就炸)+ 平台总览 | 只看自己指标 |
| 租户管理 | 创建向导(名称→域名→选出厂档案 auto/jiancai/空白→生成租户管理员)、停用/启用、域名绑定 | ✗ |
| 会话与线索 | 顶部租户切换器,看所有租户会话/画像/推送日志 | 只看自己的 |
| 配置中心 | 编辑任何租户全部配置组 | 编辑自己全部配置组(全开) |
@@ -241,8 +244,8 @@ all-kefu/
### 7.4 配置编辑形态
- 品牌:表单(颜色选择器、头像上传、文案)+ 挂件实时预览。
- 提示词:markdown 编辑器(语法高亮),侧栏显示 `$DEFLECTION_POOL` 等注入变量当前值,保存必须留 comment。
- 品牌:表单(颜色选择器、头像上传、文案)+ 挂件实时预览 + **嵌入代码复制块**(显示本租户 `<script>` 标签与域名,复制即用,替代人肉查嵌入文档)。
- 提示词:markdown 编辑器(语法高亮),侧栏显示 `$DEFLECTION_POOL` 等注入变量当前值,保存必须留 comment;预览用安全渲染(sanitize,防 markdown 内嵌 HTML 注入)。
- 词表:结构化表格(每条正则一行:语言组/表达式/备注)+ 编译校验 + 内嵌试金石。
- 邮件:SMTP 表单 + 发测试邮件;推送:目标配置 + 连通性探测(只验鉴权,不发真数据)+ 前缀唯一校验。
@@ -256,7 +259,7 @@ all-kefu/
**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 条会话抽查)。
**scripts/import_legacy.py**:老库 5 张表原结构导入对应租户库;**保留原 conversation_id**(external_key 连续,UniService 不产生重复线索);session_id/page_token/status 等字段全部原样保留(切换时刻客户凭页面令牌无缝续聊);老库 status=pending 的推送日志导入即标 failed 并加注释(切换前未完成态,新平台不重试);导入后对账报告(两会话数/消息数/推送日志数 + 最近 10 条会话抽查)。脚本幂等:按目标库已有数据跳过,重复执行不产生重复行。
**切换顺序**:新平台部署验证(不切域名)→ 导存量 → 租户域名反代从老容器切到新平台 → 观察一周(沿用 breach_rate 双向偏差观察法)→ 老项目归档下线。
@@ -268,12 +271,12 @@ chat2.agent.hnxinyouhe.com ─┼→ 1Panel 各站点反代 → 127.0.0.1:8018
admin.xxx.com(新,具体值部署前由需求方确定)─┘
```
- 一个容器一个 upstream;compose 固定 `name: all-kefu`(互删根因根治——只剩一个 compose 项目);WS 升级头单套(426 教训);`/static/widget.js` no-cache(缓存教训);`--workers 1`;容器 `TZ=Asia/Shanghai`(仪表盘"今日"边界与业务时区一致)。
- 一个容器一个 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 文件**(可能拷到写一半状态)。写入部署文档。
- 备份: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 秒切回上一版,流程写入部署文档。
- 发版回滚:镜像打版本 tag,compose 可 30 秒切回上一版,流程写入部署文档;部署文档整体继承 jc 项目 deploy-production.md 踩坑手册(1Panel include 行、面板位置联动、no-cache 等)。
- 加租户 = 后台建租户 + 1Panel 加一条反代,不起容器。
## 10. 测试策略