Files
ai-kefu-admin/CLAUDE.md
T
浅念 6cd343e24a chore: Phase 2 收尾——平台层完成(审计+安全自查+文档+清理单)
- 全量回归 566 passed(根目录与 tests/ 双跑一致),基线数字刷新(459→566)
- 零同步 IO 审计复查:app/ 无 requests/urllib/time.sleep/open,scrypt CPU 注记在案
- 安全自查全过:platform 层零 logger(凭据不进日志)、gitignore 覆盖实证
  (tenants/*.db* / platform.db* / data/)、登录限流在 /api/platform 豁免
  路径仍生效(豁免只跳过租户解析,测试钉位在)、上传目录不可执行
  (魔数+uuid 重命名,avatar 下发防路径穿透,content-type 按扩展名映射)
- CLAUDE.md:新增 Phase 2 完成状态(基线/审计/安全/平台层能力一览/
  admin API 速览)、Phase 2 沉淀取舍 10 项 + Phase 3 清理单(旧 12 项中
  #1#2#3 Task 10、#10 Task 7 已完成划除)、Phase 4 部署记档、
  Phase 3 计划编写注意(conftest 三坑 + db_source_app/super_client 夹具 +
  config_views 单入口 + stats 口径表)
- README:现状刷新至 Phase 2 收尾时点

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-13 12:21:10 +08:00

14 KiB
Raw Blame History

CLAUDE.md — all-kefu 工作约定

项目定位

本项目 all-kefu 是统一多租户 AI 客服平台:一套引擎 + 租户配置包,消除"每客户复制一套代码"的旧模式。项目由 ai-kefu / ai-kefu-jc 两个复制分叉项目合并而来,jc 为主线(代码以 ai-kefu-jc 为基础迁移,再做租户化改造)。

硬约束

  • uvicorn 必须以 --workers 1 运行。内存中的 token 缓存、限流状态、配置缓存、每租户 engine 缓存均假设单进程,多 worker 会导致状态割裂。任何启动脚本/部署配置不得改动此项。
  • Windows 环境:跑 python / pytest 前设置 PYTHONUTF8=1(GBK 控制台遇 emoji / 中文 payload 会直接崩,血泪教训)。

本地开发访问方式

  • 路由按 Host 解析租户:/ 与 /static 也需要已知租户域名(未知 Host 一律 404,唯一豁免 /health,供部署健康检查 curl 用)。
  • 本地访问二选一:改 hosts 把 chat.agent.hnxinyouhe.com / chat2.agent.hnxinyouhe.com 指到 127.0.0.1;或 curl 显式带 -H "Host: chat2.agent.hnxinyouhe.com"。
  • uvicorn 起在 8018:PYTHONUTF8=1 .venv/Scripts/python -m uvicorn app.main:app --port 8018 --workers 1。

开发流程

  • 改动 seed_profiles 后必须跑全量 pytest(出厂配置影响面大)。
  • 全量回归命令:PYTHONUTF8=1 .venv/Scripts/python -m pytest -q(根目录与 tests/ 子目录均可跑,pytest.ini 已锚定根路径)。

Phase 1 完成状态(2026-10-12 收尾核对)

测试基线:459 passed(根目录与 tests/ 子目录各跑一遍,数字一致;唯一 warning 为 starlette TestClient 对 httpx 的弃用提示,无害)。此为 Phase 1 时点数字;现行回归基线以 Phase 2 完成状态段为准(566)。

零同步 IO 审计(grep -rn "requests\.\|urllib\|time\.sleep" app/):

  • 命中数 0——app/ 内无 requests 调用、无 urllib、无 time.sleep。
  • 全库唯一 HTTP 出站点为 app/engine/services/lead_push_service.py _do_post,使用 httpx.AsyncClient(async with 上下文),异步路径成立。
  • app/engine/providers/glm.py 的同步 token 生成与兜底同步调用分别经 loop.run_in_executor / asyncio.to_thread 下卸,事件循环不被阻塞。
  • app/engine/services/rate_limit.py 的 import time 仅用 time.time() 读时钟(纯 CPU,非 IO);app/ 源码内无 open() 文件读取。

spec §6.2 合并清单核对(逐项,全部在位):

  • 取自 jc:cc_completion 完整判定树+状态机+extractor cc_reply+e164 出站渲染(app/engine/services/cc_completion.py,conversation.py 双门控消费);多语言墙(€/£、西/葡/俄/阿词表在 seed_profiles/jiancai/wall.json、ES 迂回池 boundary_filter+wall.json、建材防误杀白名单为 pattern 级 comment+后顾断言 DN800/SDR11/PN16/"euro pallets" 排除);dual-contact 分步双要(cc_settled 串行门控邮箱轻补)+_email_asked;CC 强制墙(conversation.py cc_wall 裁决段)+零号码墙回显豁免收窄(boundary_filter.py:旧豁免移除,改 allow_number 回显确认轮白名单+防自锁线);XFF 服务端优先(chat_rest _server_ip/chat.py 握手期提取,均取第一段)、阿语数字归一(contact_pipeline 捕获源头 translate)、resolve_region_code(ip_service.py)。
  • 上游吸收:input-placeholder 可配置化(品牌配置组,public.py 下发,widget/chat data-placeholder 管道);7 个测试文件(test_channel_hygiene / test_chat_rest_ip / test_lead_push_cc / test_cc_integration / test_ip_service_region / test_prompt_static / test_placeholder_static,commit d1aed8e)。
  • 三不带:blog.html(static/ 无此文件)、Redis(app+scripts 零命中)、旧版 whatsapp-cc-completion(零命中,jc 版 cc_completion.py 取代)。

当前能力一览:多租户引擎(TenantRuntime 隔离边界,engine 不感知 platform);站点模型(tenant_config.Site,一租户多站点覆盖);词表/提示词/品牌全配置化(seed_profiles 出厂包 + 文件级 registry_reload 热加载,Phase 2 升级为 platform.db);Host 路由(host_middleware,未知 Host 404);WS 防串库(租户化 client key + 会话站点标);WS 每 IP 连接上限(conn_limiter,租户级 ws_conn_limit 配置)。

Phase 2 完成状态(2026-10-13 收尾核对)

测试基线:566 passed(根目录与 tests/ 子目录各跑一遍,数字一致;唯一 warning 同 Phase 1——starlette TestClient 对 httpx 的弃用提示,无害)。后续任务回归以此为准。

零同步 IO 审计复查(grep requests / urllib / time.sleep / open 于 app/):命中数 0——无 requests/urllib 导入、无 time.sleep、无 open() 调用;import time 仅 platform/auth.py 与 engine/services/rate_limit.py 两处,均只读 time.time() 时钟。scrypt 登录路径 CPU 开销注记在案(auth.py 模块 docstring:单次 ~50ms,只在登录/改密路径,不在每请求热路径——依赖只查内存 token 表)。

安全自查(读码 + 定向测试 28 条全绿:test_auth / test_admin_mounting / test_avatar_upload / test_host_routing):

  • 密码/token 不进日志:app/platform/ 全层零 logger 调用(干净由构造保证);main.py 启动日志仅静态串 + slug/db 路径;引擎层(Phase 1 既有)日志参数为会话内容摘录/session_id/notify_emails,无凭据字段。
  • gitignore 覆盖复核(git check-ignore 实证全命中):tenants/*.db{,-wal,-shm}(租户库实际落 tenants/<slug>.db)、platform.db{,-wal,-shm}(仓库根)、data/(含 data/uploads/ 上传卷)。
  • 登录限流在 /api/platform 豁免路径仍生效:host_middleware 对 /api/platform/ 前缀的豁免只跳过租户解析,认证守护在路由依赖;登录限流先于密码验证(锁定期内正确密码也 429)。测试钉位:test_auth.py::test_login_lockout_after_5_fails + test_admin_mounting.py::test_platform_api_bypasses_tenant_resolution / test_platform_prefix_not_open / test_prefix_match_precise。
  • 上传目录不可执行:上传只认魔数(png/jpg/webp)+ uuid 重命名落盘——.py 文件无进无出;avatar 下发(public.py)取 PurePosixPath.name 并显式拒绝 / \ 与目录点(防 Windows 反斜杠穿透),FileResponse content-type 由扩展名映射(只可能 image/*)。

平台层能力一览(Phase 2 交付;engine 仍不感知 platform):

  • 认证:scrypt 版本化哈希(stdlib 零新依赖,偏离 spec bcrypt/argon2 有注记)、内存 token 8h TTL、登录限流(username+IP 滑动窗 5 次/15 分钟)、改密(must_change_password 引导)、角色两级(超管 / 租户管理员)+ 409 语义。
  • 租户管理:创建向导(消费出厂档案,blank=jc 墙提示词)、站点与域名子资源 CRUD、停用(聊天面 503、后台仍可见)、slug 不可变。
  • 配置版本管线:分组校验 → 整份版本快照(MAX+1)→ 回滚 / 重置出厂 / 试金石(wall/test)→ registry_reload 热生效。
  • 上传:头像 multipart(魔数校验 / 2MB 上限 / uuid 重命名),写配置版本 + 三级解析链下发(uploads → seed 资产 → 静态兜底)。
  • 数据视图:会话列表(筛选/分页)/ 详情 / 删除 + 推送日志——租户 scope 强制(跨租户 404)。
  • 统计:卡片 / 漏斗 / 趋势 / 分布 / 推送健康——逐租户库查询 + 应用层合并(禁 ATTACH,spec §7.6);指标口径表定死在 stats.py docstring。
  • DB 源生产装配:TENANT_SOURCE=db 默认(DbTenantSource),FileTenantSource 保留为测试/档案/冷备形态;bootstrap_platform.py 一键引导。

admin API 速览(全部 /api/platform 前缀;Host 中间件豁免该前缀的租户解析,认证由路由依赖守护——require_super 超管 / _tenant_guard 租户管理员):

  • 认证:POST /auth/login、POST /auth/logout、POST /auth/change-password
  • 租户:GET|POST /tenants、GET|PATCH /tenants/{slug}、POST /tenants/{slug}/sites、PATCH /tenants/{slug}/sites/{site_slug}、POST|DELETE /tenants/{slug}/sites/{site_slug}/domains[/{domain}]
  • 配置:GET /tenants/{slug}/config、PUT /tenants/{slug}/config/{group}、POST /tenants/{slug}/config/{group}/reset、GET /tenants/{slug}/config/versions[/{version}]、POST /tenants/{slug}/config/rollback、POST /tenants/{slug}/wall/test
  • 上传:POST /tenants/{slug}/brand/avatar
  • 数据:GET /tenants/{slug}/conversations[/{conv_id}]、DELETE /tenants/{slug}/conversations/{conv_id}、GET /tenants/{slug}/push-logs
  • 统计:GET /stats(超管,?tenant= 单租户下钻)、GET /tenants/{slug}/stats
  • 平台设置:GET|PUT /settings

Phase 2 沉淀的取舍与 Phase 3 清理单

实现取舍(API 冻结 / Phase 3 前端对接前须知):

  1. PUT config 整组替换语义——前端必须整组读-改-写(Task 6;做部分更新需先 GET 全组再改再 PUT)。
  2. rollback 绕过 prefix 跨租户查重(域名类校验只在 PUT 路径)——如需收口,在 rollback 前补跑查重。
  3. 并发保存同租户撞 UNIQUE → 500(单管理员场景可接受;收口 = 捕 IntegrityError 转 409)。
  4. 统计 API 无 ?site= 筛选(spec §7.6 提及;Phase 3 前端需要时补)。
  5. 登录限流反代形态退化为按用户名(限流键 username + request.client.host,无 XFF 解析;可信代理解析归部署批次)。
  6. avatar 旧文件不清理(换头像后旧文件留盘;孤儿累积无害)。
  7. 停用租户管理员存量 token 最长存活 8h(TTL 限窗)——即时锁死需在 validate 时回查租户状态。
  8. 400(预检撞车)vs 409(竞态 IntegrityError)并存——API 冻结前统一。
  9. limiter 全量清扫(token/登录限流的内存键永不回访,长跑进程缓慢累积;收口 = 定期清扫)。
  10. lead_push_service 的 settings re-export 锚(app.engine.config 的 settings 在 lead_push_service 顶部 re-export,22 处测试 monkeypatch 依赖此锚——改动前先迁测试)。

Phase 3 清理单(Phase 1/2 审查挂账滚动合并;删除线 项已在 Phase 2 完成):

  1. FakeAI 三份拷贝收敛进 conftest(Task 10 完成:conftest.py 单一定义,tests/ 零拷贝)。
  2. rate_limit 与 ws_conn_limit 的 0 语义统一 + 配置类型校验(Task 10 完成)。
  3. name_to_region_extras / context_seed_map 等散装 resolver 收拢为 config_views 单入口(Task 10 完成:app/platform/config_views.py)。
  4. avatar 从 seed_profiles 迁 uploads 卷(Task 7 完成:上传 + 三级解析链)。
  5. AIService.from_runtime 类方法——组装正典化(出处:引擎改造审查)。
  6. boundary_filter 视图常量退役——admin 重写后旧测试迁移时(出处:引擎改造审查)。
  7. push enabled 布尔空槽语义——admin 配置显式化时解决(出处:push 审查)。
  8. contact.template_languages 运行期接线——后台批次(出处:配置 schema 审查)。
  9. 话术含引号/换行的转义——编辑器批次(_format_deflection_pool docstring 有注)。
  10. 产品决策项:国名纠正 e164 变化时是否补发更正邮件(上游行为更优);REST/WS 服务端 IP 口径是否统一(出处:上游吸收审查)。
  11. /health 分级健康——Phase 4 部署批次(进程存活之外按依赖分级上报;出处:终审审查 M-3)。
  12. PromptProvider.from_dir 的 FileNotFoundError 带路径上下文——提示词目录缺失时异常信息含实际路径(出处:终审审查 M-4)。

Phase 3 计划编写注意

  • conftest 三坑纪律延续:async_db 夹具用 NullPool;TestClient 必须走 portal 桥接事件循环;禁止 asyncio.run 直开租户 session_factory(三个坑都在 tests/conftest.py 有既成解法,照抄勿发明)。
  • build_app / app_client / TenantTestClient 夹具已就绪,新测试优先复用而非自建 app 实例;平台层测试用 db_source_app / super_client 夹具(conftest.py:DB 源装配 + 超管登录态客户端,Phase 2 全部平台路由测试的既成姿势)。
  • 引擎侧读配置一律经 config_views 单入口(app/platform/config_views.py:resolve_email/push/ai_settings、resolve_contact_flags、context_seed_map、name_to_region_extras)——新消费点不再自写 resolver。
  • 统计前端照 stats.py docstring 的指标口径表展示(会话数不要求有消息、线索=三选一非空、推送成功率分母口径、北京日界趋势等,歧义处已定死)。

Phase 4 部署记档(提前锚定,部署批次展开成 docs/deploy-production.md)

  • pip install -r requirements.txt 必须执行:python-multipart(>=0.0.20)为 Phase 2 新依赖——Task 7 实证缺失即头像上传 500(FastAPI multipart 解析依赖它)。
  • bootstrap_platform.py 用法:PYTHONUTF8=1 python scripts/bootstrap_platform.py [--db PATH] [--profiles SLUG ...] [--seed DIR]。两步全幂等:init(schema + 超管引导,凭据取 env/.env 的 ADMIN_USERNAME/ADMIN_PASSWORD)+ 导入出厂租户(默认 auto jiancai 两租户,已有 slug 跳过);不建租户管理员(走后台创建向导补)。
  • TENANT_SOURCE=db 默认(生产装配形态);file 为冷备/本地调试回退。
  • admin 域名反代区分:/api/platform/* 前缀豁免租户 Host 解析——平台后台 API 可与租户聊天域名同源反代(按路径分流)或独立域名,两形态皆可;租户聊天面(/、/static、WS、/api/public、/api/chat*)仍严格按 Host 解析租户,未知 Host 404。

文档索引

  • 设计文档(spec):docs/superpowers/specs/2026-10-12-unified-kefu-platform-design.md
  • Phase 1 实施计划:docs/superpowers/plans/2026-10-12-phase1-engine-merge-tenantization.md
  • Phase 2 实施计划:docs/superpowers/plans/2026-10-13-phase2-platform-layer.md
  • 部署坑手册:Phase 4 补 docs/deploy-production.md(尚未存在;Phase 4 部署记档段已锚定要点)