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>
This commit is contained in:
浅念
2026-10-19 19:44:46 +08:00
parent e1eae007b4
commit 9315dba228
2 changed files with 112 additions and 5 deletions
+67 -5
View File
@@ -4,7 +4,8 @@
# 拓扑(spec §9):所有域名(租户域名 × N + 管理后台)反代到同一个容器 # 拓扑(spec §9):所有域名(租户域名 × N + 管理后台)反代到同一个容器
# chat.agent.hnxinyouhe.com (租户 auto) ─┐ # chat.agent.hnxinyouhe.com (租户 auto) ─┐
# chat2.agent.hnxinyouhe.com (租户 jiancai) ─┼→ 127.0.0.1:8018 → all-kefu 容器 # chat2.agent.hnxinyouhe.com (租户 jiancai) ─┼→ 127.0.0.1:8018 → all-kefu 容器
# <admin 域名>(形态 A,部署前由需求方确定)─┘ # <admin 域名>(形态 A,部署前由需求方确定)─┤
# <共享域名>(路径模式 /t/<slug>/,见下方共享域名 server 块)─┘
# #
# 两种用法: # 两种用法:
# · 独立 openresty/nginx:整份可抄——upstream + server 块齐备,按需删掉 # · 独立 openresty/nginx:整份可抄——upstream + server 块齐备,按需删掉
@@ -32,6 +33,15 @@ upstream all_kefu {
# 含:全站反代 + WS 单套升级头 + /api/public(widget 头像/品牌,放行即全站 # 含:全站反代 + WS 单套升级头 + /api/public(widget 头像/品牌,放行即全站
# 反代的一部分,单独点名是防"按路径收紧"时漏掉它——漏配的症状是 widget # 反代的一部分,单独点名是防"按路径收紧"时漏掉它——漏配的症状是 widget
# 头像 404)。 # 头像 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 { server {
listen 80; # 1Panel/证书层负责 443 与 HTTPS;独立部署自行补 listen 443 ssl listen 80; # 1Panel/证书层负责 443 与 HTTPS;独立部署自行补 listen 443 ssl
server_name chat2.agent.hnxinyouhe.com; # ← 每个租户域名一份:chat.(auto)/ chat2.(jiancai) server_name chat2.agent.hnxinyouhe.com; # ← 每个租户域名一份:chat.(auto)/ chat2.(jiancai)
@@ -44,7 +54,7 @@ server {
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
proxy_set_header Host $host; # 纪律 1:WS 也按 Host 解析租户 proxy_set_header Host $host; # 纪律 1:WS 也按 Host 解析租户
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 引擎 IP/区域解析取 XFF 第一段 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 首段=引擎 IP/区域、末段=登录限流(注记见块首)
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 3600s; proxy_read_timeout 3600s;
proxy_send_timeout 3600s; proxy_send_timeout 3600s;
@@ -58,7 +68,7 @@ server {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $host; # 纪律 1 proxy_set_header Host $host; # 纪律 1
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加式,勿改覆盖式(注记见块首)
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s; proxy_read_timeout 300s;
client_max_body_size 5m; # 纪律 3(头像上传) client_max_body_size 5m; # 纪律 3(头像上传)
@@ -94,6 +104,55 @@ server {
# } # }
} }
# ---- 共享域名 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 形态 A:独立管理域名 server 块(与形态 B 二选一) ------------------
# 整域反代到同一容器:/admin(SPA 静态,含 assets)与 /api/platform(后台 # 整域反代到同一容器:/admin(SPA 静态,含 assets)与 /api/platform(后台
# API,认证由路由依赖守护)同一 upstream。全站反代即可,无需按路径拆分。 # API,认证由路由依赖守护)同一 upstream。全站反代即可,无需按路径拆分。
@@ -112,7 +171,7 @@ server {
proxy_set_header Host $host; # 形态 A 的 Host 是管理域(未知租户)—— proxy_set_header Host $host; # 形态 A 的 Host 是管理域(未知租户)——
# /admin 与 /api/platform 豁免租户解析,照常工作 # /admin 与 /api/platform 豁免租户解析,照常工作
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 追加式,勿改覆盖式(注记见租户域名块)
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 300s; proxy_read_timeout 300s;
client_max_body_size 5m; # 头像上传 client_max_body_size 5m; # 头像上传
@@ -125,7 +184,10 @@ server {
# 1. server 块由面板建站生成,不要手写整个 server——只把上面的 location # 1. server 块由面板建站生成,不要手写整个 server——只把上面的 location
# 片段放进站点目录: # 片段放进站点目录:
# /opt/1panel/www/sites/<域名>/proxy/root.conf ← location /(全站反代) # /opt/1panel/www/sites/<域名>/proxy/root.conf ← location /(全站反代)
# /opt/1panel/www/sites/<域名>/proxy/ws.conf ← location /ws/(升级头单套) # /opt/1panel/www/sites/<域名>/proxy/ws.conf ← location /ws/(升级头单套;
# 共享域名站为 regex 形态
# `~ ^/t/[a-z0-9-]+/ws/`,
# 见上方共享域名块)
# /opt/1panel/www/sites/<域名>/proxy/admin.conf ← 形态 B 的两块(启用时) # /opt/1panel/www/sites/<域名>/proxy/admin.conf ← 形态 B 的两块(启用时)
# 片段内 proxy_pass 直接写 http://127.0.0.1:8018——upstream 块只能声明在 # 片段内 proxy_pass 直接写 http://127.0.0.1:8018——upstream 块只能声明在
# http 上下文,server 级 include 文件里放不进去(面板世界没有"全局 # http 上下文,server 级 include 文件里放不进去(面板世界没有"全局
+45
View File
@@ -210,6 +210,49 @@ curl -s -i --http1.1 -H "Connection: Upgrade" -H "Upgrade: websocket" \
https://chat2.agent.hnxinyouhe.com/ws/chat | head -1 # 期待 HTTP/1.1 101 https://chat2.agent.hnxinyouhe.com/ws/chat | head -1 # 期待 HTTP/1.1 101
``` ```
### 3.6 共享域名模式(单域名多租户,可选)
> 设计出处:spec `docs/superpowers/specs/2026-10-19-shared-domain-routing-design.md`。
**概念**:一个共享域名(如 `kefu.hnxinyouhe.com`)承载任意多租户——租户标识
在 URL 路径前缀 `/t/<租户slug>/` 中,与 §3.1 "每租户一域名"的 Host 模式
**共存**,解析优先级:**路径前缀 > Host**(`/t/<slug>/...` 命中即剥前缀按
slug 解析租户;不带前缀的请求照走 Host 解析——共享域名自身不是租户域名,
根路径 404 属预期)。
**接入流程(零 DNS 操作)**:
1. 平台设置(仅超管)配 `shared_widget_domain` = 共享域名(运营填一次);
2. 建租户——域名可整组留空(纯路径模式租户,创建向导支持跳过域名);
3. 配置中心 → 品牌与外观 → 嵌入代码 → 复制「共享域名形态」→ 贴到目标网站。
**地址形态**:
| 项 | 形态 |
|---|---|
| 聊天页 | `https://<共享域名>/t/<slug>/` |
| 挂件脚本 | `https://<共享域名>/t/<slug>/static/widget.js`(脚本地址即租户标识,无需新参数) |
| `data-site` 参数 | 照常可用;缺省取租户首个站点 |
**无尾斜杠语义**:`/t/<slug>`(无 `/`)→ **404**——仅带尾斜杠形态才剥离
前缀(spec §3 防歧义设计,不做 308 重定向);嵌入代码生成的就是带斜杠正确
形态,勿手工"修正"。
**部署注记**:
- 共享域名 = **一条反代**(1Panel 一站),§3.2 三件事同款:透传 Host 头、
WS 升级头单套、`client_max_body_size 5m`;WS 超时 3600s 同既有。模板
`deploy/openresty-all-kefu.conf.template` 有现成注释示例块(`kefu.example.com`
占位)。**WS 位置差异**:路径模式 WS 的真实路径是 `/t/<slug>/ws/chat`——
升级头必须落在 regex location `~ ^/t/[a-z0-9-]+/ws/`(照抄模板共享域名块;
1Panel 同样放 `proxy/ws.conf`),只配 `location /ws/` 匹配不到它(症状:
聊天页能开、WS 连不上)。
- **uvicorn 不得加 `--root-path`**:路径前缀解析在 raw_path 上做,root_path
会前移 raw_path → `/t/` 前缀永不命中,全站退化为 Host 解析 404(同模板注记)。
- **登录限流取 XFF 末段**(`app/platform/routes/auth_routes.py` `_client_ip`)
——依赖反代 `$proxy_add_x_forwarded_for` 的**追加**语义(客户端自带在前、
真实来源追加在尾);勿改成 `X-Forwarded-For $remote_addr` 覆盖式(同模板注记)。
## 4. 存量迁移(老 jc / auto 项目 → 本平台) ## 4. 存量迁移(老 jc / auto 项目 → 本平台)
**前提**:老项目与本项目同机(47.254.176.10);若跨机,先把导出文件 scp 到 **前提**:老项目与本项目同机(47.254.176.10);若跨机,先把导出文件 scp 到
@@ -411,6 +454,8 @@ curl -s http://127.0.0.1:8018/health
**加租户 = 后台建租户 + 1Panel 反代加一条,不起容器**: **加租户 = 后台建租户 + 1Panel 反代加一条,不起容器**:
后台"租户管理 → 创建向导"(消费出厂档案/空档案)→ 站点绑定新域名 → 1Panel 后台"租户管理 → 创建向导"(消费出厂档案/空档案)→ 站点绑定新域名 → 1Panel
为新域名建站反代 `127.0.0.1:8018`(§3 同款三件事)→ 完成。同一容器自动承载。 为新域名建站反代 `127.0.0.1:8018`(§3 同款三件事)→ 完成。同一容器自动承载。
已启用共享域名(§3.6)时更省:新租户零域名、零 DNS、零反代操作——直接按
`/t/<slug>/` 形态取嵌入代码即完成接入。
### 6.6 bootstrap 复跑 ### 6.6 bootstrap 复跑