Files
ai-kefu-admin/deploy/openresty-all-kefu.conf.template
T
浅念 9315dba228 docs(deploy): 共享域名模式部署工件
模板:共享域名 server 块注释示例(kefu.example.com 占位,WS 升级头
落 regex location ^/t/[a-z0-9-]+/ws/——路径模式 WS 真实路径带 /t 前缀);
XFF 追加式语义注记(登录限流取末段,勿改覆盖式);--root-path 禁用
边界(root_path 前移 raw_path,路径前缀解析永不命中)。

手册:§3.6 共享域名模式(概念/零 DNS 接入流程/地址形态表/无尾斜杠
404 语义/部署注记三条);§6.5 加租户补共享域名零反代路径;1Panel
注记补 ws.conf regex 形态。

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-19 19:44:46 +08:00

216 lines
13 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# all-kefu 生产反代模板(openresty / nginx 通用,完整可抄)
# =============================================================================
# 拓扑(spec §9):所有域名(租户域名 × N + 管理后台)反代到同一个容器
# chat.agent.hnxinyouhe.com (租户 auto) ─┐
# chat2.agent.hnxinyouhe.com (租户 jiancai) ─┼→ 127.0.0.1:8018 → all-kefu 容器
# <admin 域名>(形态 A,部署前由需求方确定)─┤
# <共享域名>(路径模式 /t/<slug>/,见下方共享域名 server 块)─┘
#
# 两种用法:
# · 独立 openresty/nginx:整份可抄——upstream + server 块齐备,按需删掉
# 不用的块(admin 只用形态 A 或 B 其一)。
# · 1Panel:server 块由面板生成,**只抄 location 片段**到
# /opt/1panel/www/sites/<域名>/proxy/*.conf——文末「1Panel 适配注记」
# 有逐条对照与现成片段。
#
# 血泪纪律(每条都出过线上事故,改配置前重读一遍):
# 1. Host 头必须透传(proxy_set_header Host $host)——本项目按 Host 头
# 解析租户,漏了这一行全站 404(不是报错,是静默 404)。
# 2. WS 升级头(Upgrade/Connection)全站只能出现一套——1Panel 面板生成
# 的反代若已带一套,再抄一份会重复定义,uvicorn 严格解析直接拒绝
# 握手(HTTP 正常、WS 426)。
# 3. client_max_body_size 至少 5m——头像上传上限 2MB,nginx 默认 1m
# 会直接 413(应用层校验根本收不到请求)。
# =============================================================================
# ---- upstream(独立部署时用;1Panel 场景见文末注记,直接写 IP:端口) --------
upstream all_kefu {
server 127.0.0.1:8018; # 单容器单 upstream;换端口只改这里
}
# ---- 租户域名 server 块(每个租户域名复制一份,改 server_name 即可) --------
# 含:全站反代 + WS 单套升级头 + /api/public(widget 头像/品牌,放行即全站
# 反代的一部分,单独点名是防"按路径收紧"时漏掉它——漏配的症状是 widget
# 头像 404)。
#
# XFF 注记(本模板所有 `X-Forwarded-For $proxy_add_x_forwarded_for` 行同款
# 语义,含 /ws/、admin 两形态与共享域名块——改任何一处前先读这里):
# 登录限流取 XFF **末段**(app/platform/routes/auth_routes.py _client_ip)
# ——依赖本指令的**追加**语义(客户端自带 XFF 在前、反代把真实来源
# $remote_addr 追加在尾)。**勿改成 `X-Forwarded-For $remote_addr` 覆盖式**
# ——多 CDN 场景语义漂移;单跳拓扑下两者等价,但保持追加式。同一行的
# 首段是引擎 IP/区域解析的消费位(取第一段),两个消费方共用这一行,
# 动它两头都伤。
server {
listen 80; # 1Panel/证书层负责 443 与 HTTPS;独立部署自行补 listen 443 ssl
server_name chat2.agent.hnxinyouhe.com; # ← 每个租户域名一份:chat.(auto)/ chat2.(jiancai)
# WebSocket(/ws/chat):升级头单套 + 长读超时(长连接心跳间隔内不断开)
location /ws/ {
proxy_pass http://all_kefu;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host; # 纪律 1:WS 也按 Host 解析租户
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 首段=引擎 IP/区域、末段=登录限流(注记见块首)
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
# 其余全部(/, /static, /api/chat*, /api/public, /health …)——
# /static/widget.js 的 no-cache 由应用层中间件下发(响应头已带
# Cache-Control: no-cache),反代无需另加,也别加会覆盖它的 expires。
location / {
proxy_pass http://all_kefu;
proxy_http_version 1.1;
proxy_set_header Host $host; # 纪律 1
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加式,勿改覆盖式(注记见块首)
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
client_max_body_size 5m; # 纪律 3(头像上传)
}
# ---- admin 形态 B(同域名路径分流;不用独立管理域名时启用) --------------
# 应用层对 /admin 与 /api/platform/ 前缀豁免租户 Host 解析,挂在租户
# 域名下照常工作(与租户聊天面共存,同源零 CORS)。启用 = 取消注释;
# nginx 最长前缀匹配,这两块优先于上面的 location /。
# 已知边界:形态 B 下后台"品牌-头像预览"走本域 /api/public/avatar,
# 解析到的是**本域名对应的租户**——切换到其他租户的配置页时头像预览
# 不准(已知项,CLAUDE.md Phase 4 清理单 #3/#4)。
#
# location /admin {
# proxy_pass http://all_kefu;
# proxy_http_version 1.1;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_read_timeout 300s;
# client_max_body_size 5m; # 头像上传走 /api/platform/.../brand/avatar
# }
# location /api/platform/ {
# proxy_pass http://all_kefu;
# proxy_http_version 1.1;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_read_timeout 300s;
# client_max_body_size 5m;
# }
}
# ---- 共享域名 server 块(路径模式 /t/<slug>/,注释示例——启用 = 取消注释) ----
# 单域名承载任意多租户(spec 2026-10-19,部署手册 §3.6):租户标识在 URL
# 路径 /t/<slug>/ 中,与上方"每租户一域名"的 Host 模式共存(优先级:
# 路径前缀 > Host)。接入零 DNS:平台设置配 shared_widget_domain → 建租户
# (域名可留空)→ 后台复制"共享域名形态"嵌入代码贴到目标网站。地址形态:
# 聊天页 https://<共享域名>/t/<slug>/ 挂件 .../t/<slug>/static/widget.js
# 已知边界(照抄前先读):
# 1. 路径模式 WS 的真实路径是 /t/<slug>/ws/chat(widget.js 按 basePath
# 拼接),不带 /t 前缀的 `location /ws/` 匹配不到它——升级头用下面
# 的 regex location 承载(nginx regex location 优先于前缀 location /,
# 正好圈中"路径模式 + /ws/"的交集;1Panel 同样放 proxy/ws.conf)。
# 2. 路径前缀模式要求应用以**域名根**部署——uvicorn 不得加 `--root-path`
# (root_path 会前移 raw_path,/t/ 前缀解析永不命中,全站退化为 Host
# 解析 404)。
# 3. `/t/<slug>`(无尾斜杠)→ 404 属预期(仅带尾斜杠形态剥离前缀);
# 嵌入代码生成的就是带斜杠正确形态,勿手工"修正"。
#server {
# listen 80;
# server_name kefu.example.com; # ← 占位:共享域名部署前由需求方确定
#
# # WebSocket(路径模式 /t/<slug>/ws/chat):升级头单套 + 长读超时
# location ~ ^/t/[a-z0-9-]+/ws/ {
# proxy_pass http://all_kefu;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# proxy_set_header Host $host; # 纪律 1:路径模式按 /t/<slug> 解析租户,
# # 但 Host 头仍须透传(豁免前缀按它工作)
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加式,勿改覆盖式(注记见租户域名块)
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_read_timeout 3600s;
# proxy_send_timeout 3600s;
# }
#
# # 其余全部(/t/<slug>/ 聊天页、/t/<slug>/static/*、/t/<slug>/api/*、
# # /health …)——同租户域名块:no-cache 由应用层下发,反代别加 expires。
# location / {
# proxy_pass http://all_kefu;
# proxy_http_version 1.1;
# proxy_set_header Host $host; # 纪律 1
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加式,勿改覆盖式(注记见租户域名块)
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_read_timeout 300s;
# client_max_body_size 5m; # 纪律 3
# }
#}
# ---- admin 形态 A:独立管理域名 server 块(与形态 B 二选一) ------------------
# 整域反代到同一容器:/admin(SPA 静态,含 assets)与 /api/platform(后台
# API,认证由路由依赖守护)同一 upstream。全站反代即可,无需按路径拆分。
# 入口是 https://<admin 域名>/admin/——管理域根路径 / 走租户 Host 解析,
# 404 属预期(要友好可在本块加 location = / { return 302 /admin/; })。
# 已知边界:独立管理域名下 /api/public/*(按租户 Host 解析)必 404——后台
# "品牌-头像预览"在该形态加载不出(预览以本地简化预览为准),已知项
# (CLAUDE.md Phase 4 清理单 #3),非配置错误,勿按 404 排障反代。
server {
listen 80;
server_name admin.example.com; # ← 占位:管理域名部署前由需求方确定
location / {
proxy_pass http://all_kefu;
proxy_http_version 1.1;
proxy_set_header Host $host; # 形态 A 的 Host 是管理域(未知租户)——
# /admin 与 /api/platform 豁免租户解析,照常工作
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加式,勿改覆盖式(注记见租户域名块)
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s;
client_max_body_size 5m; # 头像上传
}
}
# =============================================================================
# 1Panel 适配注记(jc 项目两次线上事故的源头,逐条核对)
# =============================================================================
# 1. server 块由面板建站生成,不要手写整个 server——只把上面的 location
# 片段放进站点目录:
# /opt/1panel/www/sites/<域名>/proxy/root.conf ← location /(全站反代)
# /opt/1panel/www/sites/<域名>/proxy/ws.conf ← location /ws/(升级头单套;
# 共享域名站为 regex 形态
# `~ ^/t/[a-z0-9-]+/ws/`,
# 见上方共享域名块)
# /opt/1panel/www/sites/<域名>/proxy/admin.conf ← 形态 B 的两块(启用时)
# 片段内 proxy_pass 直接写 http://127.0.0.1:8018——upstream 块只能声明在
# http 上下文,server 级 include 文件里放不进去(面板世界没有"全局
# upstream 文件"的挂载点,直写地址即单一事实源,换端口全文替换)。
#
# 2. 主 conf 的 include 行检查(1Panel 坑:面板建站默认可能不带):
# /opt/1panel/www/conf.d/<域名>.conf
# 里必须有(没有就手工补,改前备份同目录 *.conf.bak.<日期>):
# include /www/sites/<域名>/proxy/*.conf;
# 注意路径前缀是 /www/sites/...(1Panel 容器内视角),不是 /opt/1panel/...。
# 面板配了反代却不生效,九成是这行缺失。
#
# 3. 1Panel 面板生成的反代若自带 Upgrade/Connection 头,删掉面板那套再放
# ws.conf(升级头只能一套,见顶部纪律 2)。
#
# 4. 改完必验再重载(先 -t 再 reload;reload 平滑,不影响其他站点):
# docker exec $(docker ps --filter name=openresty -q | head -1) nginx -t
# docker exec $(docker ps --filter name=openresty -q | head -1) nginx -s reload
# 验证(逐域名):
# curl -s https://<租户域名>/health | head -c 200 # {"status":"ok",...}
# curl -sI https://<租户域名>/static/widget.js | grep -i cache # Cache-Control: no-cache
# curl -s -i --http1.1 -H "Connection: Upgrade" -H "Upgrade: websocket" \
# -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" \
# https://<租户域名>/ws/chat | head -1 # 期待 HTTP/1.1 101
# =============================================================================