Files
beaver_project/域名配置指引.md
steven_li 83d9d8c200 ```
feat(learning): 添加技能学习候选者合成锁定机制

添加了 DraftSynthesisInProgress 和 DraftHasNoChanges 异常来处理并发场景,
确保同一技能学习候选者的合成过程不会重复执行。实现了 claim_learning_candidate_for_synthesis
方法来原子性地锁定候选者进行合成。

fix(web): 为技能草案创建端点添加适当的HTTP状态码

当草案没有变化或正在合成时,现在正确返回409状态码而不是内部错误。

feat(skills): 实现技能修订内容比较以检测无变化情况

添加了 _is_noop_revision 方法来比较基础技能和提议的修订,
如果内容没有实际变化则抛出 NoDraftChanges 异常。

refactor(process): 修复任务证据记录后根运行状态更新逻辑

将任务证据记录事件后的状态从 waiting 更改为 done,并设置 finished_at 时间戳。

feat(tools): 防止在同一运行中重复执行外部写入操作

为邮件发送、日历创建等外部写入工具添加去重机制,避免重复的外部操作。

test: 添加技能学习和工具执行的单元测试

增加测试用例验证并发草案合成、重复外部写入抑制和无变化修订检测等功能。
```
2026-06-16 15:58:42 +08:00

375 lines
9.7 KiB
Markdown
Raw Permalink 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.

# Beaver Project 域名配置指引
最后更新2026-06-16。
这份文档说明如何从本机测试域名 `localhost` 子域名切换到正式域名。
核心结论:
- DNS 只负责把域名解析到 IP。
- DNS 不负责端口。
- `auth-portal` 和用户实例建议使用不同域名。
- 正式环境建议用外层 Nginx、Caddy、Traefik 或云负载均衡监听 `80/443`
- `router-proxy` 必须收到原始 `Host` 头,才能按实例域名转发。
- 正式实例入口推荐使用真实域名;不要用裸 IP 当实例基域名,除非你明确要走每实例直连端口模式。
## 1. 默认端口职责
| 端口 | 组件 | 是否建议公网直接暴露 |
| --- | --- | --- |
| `3081` | `auth-portal`,用户登录和注册入口 | 可以,或由外层代理转发 |
| `8088` | `router-proxy`,所有实例统一入口 | 可以,或由外层代理转发 |
| `8090` | `deploy-control`,内部部署控制面 | 不建议 |
| `19090` | `authz-service`,内部鉴权服务 | 不建议 |
| `8787` | `external-connector` sidecar 管理/调试口 | 不建议 |
| `9000/9001` | 本地 MinIO S3 API / Console | 不建议 |
| `20000-29999` | app-instance 直连端口池,通常绑定 `127.0.0.1`,裸 IP 模式可能对外绑定 | 不建议 |
正式部署时,通常由外层入口暴露 `80/443`,再转发到本机端口:
```text
portal.example.com -> 127.0.0.1:3081
*.apps.example.com -> 127.0.0.1:8088
```
## 2. 推荐域名规划
推荐拆成两个域名空间:
```text
https://portal.example.com
https://alice.apps.example.com
https://bob.apps.example.com
```
含义:
- `portal.example.com``auth-portal`
- `*.apps.example.com``router-proxy`
- 每个实例使用一个子域名,例如 `alice.apps.example.com`
不要把门户和实例混在同一个 Host 上。`router-proxy` 是实例入口,`auth-portal` 是认证入口,两者职责不同。
## 3. DNS 要怎么配
假设服务器公网 IP 是 `203.0.113.10`
DNS 记录:
```text
portal.example.com A 203.0.113.10
apps.example.com A 203.0.113.10
*.apps.example.com A 203.0.113.10
```
如果你的 DNS 服务商支持 CNAME也可以让通配子域名 CNAME 到一个已有 A 记录,但最终结果仍然必须能解析到服务器入口 IP。
注意:
- `*.apps.example.com` 用于实例子域名。
- `apps.example.com` 本身不是必须给用户访问,但建议也解析到同一入口,方便证书和排查。
- DNS 不会决定 `3081``8088``443` 这些端口。
## 4. 外层反向代理要做什么
外层代理负责:
- 监听公网 `80/443`
- 处理 TLS 证书
- 按 Host 转发请求
- 透传原始 Host
- 支持 WebSocket upgrade
最小映射:
```text
portal.example.com -> http://127.0.0.1:3081
*.apps.example.com -> http://127.0.0.1:8088
```
`*.apps.example.com` 转发到 `router-proxy` 时必须保留原始 Host例如
```nginx
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
```
否则 `router-proxy` 无法知道请求属于哪个实例。
如果需要支持用户文件系统的大文件上传,外层代理还要允许足够大的 body。项目内 app-instance Nginx 当前是 `client_max_body_size 5g`,外层 Nginx/Caddy/负载均衡的限制不能比实际业务需求更小。
## 5. 项目内部要改哪些变量
实例公网地址由 `deploy-control` 里的这些变量决定:
| 变量 | 含义 |
| --- | --- |
| `DEPLOY_PUBLIC_SCHEME` | 对外协议,`http``https` |
| `DEPLOY_PUBLIC_BASE_DOMAIN` | 实例基域名,例如 `apps.example.com` |
| `DEPLOY_PUBLIC_HOST_TEMPLATE` | Host 生成模板,默认 `{slug}.{base_domain}` |
| `DEPLOY_PUBLIC_PORT` | 对外端口,`80` / `443` 会在生成 URL 时省略 |
| `DEPLOY_DIRECT_PUBLIC_HOST_BIND_IP` | 仅裸 IP 基域名直连模式使用,控制实例宿主机端口绑定地址 |
本机测试:
```bash
export BEAVER_BASE_DOMAIN=localhost
```
`deploy-control`
```bash
-e DEPLOY_PUBLIC_SCHEME="http" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="$BEAVER_BASE_DOMAIN" \
-e DEPLOY_PUBLIC_PORT="8088" \
```
生成实例地址:
```text
http://alice.localhost:8088
```
正式 HTTPS
```bash
export BEAVER_BASE_DOMAIN=apps.example.com
```
`deploy-control`
```bash
-e DEPLOY_PUBLIC_SCHEME="https" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="apps.example.com" \
-e DEPLOY_PUBLIC_PORT="443" \
```
生成实例地址:
```text
https://alice.apps.example.com
```
前提是外层代理已经把 `*.apps.example.com:443` 转发到 `router-proxy:8088`
裸 IP 特例:
```bash
export BEAVER_BASE_DOMAIN=203.0.113.10
```
`DEPLOY_PUBLIC_BASE_DOMAIN` 是 IP 地址时,`deploy-control` 会进入直连端口模式:每个实例从 `20000-29999` 端口池分配一个宿主机端口,生成类似:
```text
http://203.0.113.10:20037
```
这不是 `router-proxy` 的 Host 路由入口,也无法得到 `https://alice.apps.example.com` 这类实例子域名。正式环境推荐使用 `apps.example.com` 这类真实域名和通配 DNS。
## 6. 什么时候 URL 里可以不带端口
浏览器默认端口:
- `http` 默认 `80`
- `https` 默认 `443`
所以:
```bash
DEPLOY_PUBLIC_SCHEME=https
DEPLOY_PUBLIC_PORT=443
```
生成:
```text
https://alice.apps.example.com
```
而不是:
```text
https://alice.apps.example.com:443
```
如果你设置:
```bash
DEPLOY_PUBLIC_SCHEME=http
DEPLOY_PUBLIC_PORT=8088
```
生成:
```text
http://alice.apps.example.com:8088
```
因为 `8088` 不是 HTTP 默认端口。
## 7. 一套推荐生产配置
假设:
- 门户域名:`portal.example.com`
- 实例基域名:`apps.example.com`
- 外层代理负责 HTTPS
- 项目基础容器仍在同一台机器上通过 Docker 运行
部署变量:
```bash
export BEAVER_BASE_DOMAIN=apps.example.com
export BEAVER_AUTHZ_URL='http://beaver-authz-service:19090'
export BEAVER_DEPLOY_URL='http://beaver-deploy-control:8090'
```
`deploy-control`
```bash
-e DEPLOY_PUBLIC_SCHEME="https" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="apps.example.com" \
-e DEPLOY_PUBLIC_PORT="443" \
```
外层代理:
```text
portal.example.com -> 127.0.0.1:3081
*.apps.example.com -> 127.0.0.1:8088
```
DNS
```text
portal.example.com -> 服务器 IP
apps.example.com -> 服务器 IP
*.apps.example.com -> 服务器 IP
```
正常域名部署不依赖 `DEPLOY_DIRECT_PUBLIC_HOST_BIND_IP`;它只影响裸 IP 直连端口模式。生产入口应优先让外层代理监听 `80/443`,再转发到本机 `3081``8088`
## 8. Nginx 外层代理示例
示例只展示关键转发逻辑,证书路径和自动签发方式按你的环境调整。
```nginx
server {
listen 80;
server_name portal.example.com *.apps.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name portal.example.com;
ssl_certificate /etc/letsencrypt/live/portal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/portal.example.com/privkey.pem;
location / {
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_pass http://127.0.0.1:3081;
}
}
server {
listen 443 ssl http2;
server_name *.apps.example.com;
ssl_certificate /etc/letsencrypt/live/apps.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/apps.example.com/privkey.pem;
location / {
client_max_body_size 5g;
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_set_header X-Forwarded-Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_pass http://127.0.0.1:8088;
}
}
```
证书注意:
- `portal.example.com` 可以用普通单域名证书。
- `*.apps.example.com` 需要通配符证书,通常要用 DNS-01 验证。
- 也可以用支持自动证书的 Caddy / Traefik 简化这层。
## 9. 常见误区
### DNS 不能配置端口
DNS 只能做:
```text
域名 -> IP
```
端口来自:
- URL 里显式端口
- 协议默认端口
- 外层代理监听和转发规则
### 不要把 portal 转到 8088
`8088` 是实例入口,不是认证门户入口。
推荐:
```text
portal.example.com -> 3081
*.apps.example.com -> 8088
```
### 不要公开内部端口
`8090` 是部署控制面,`19090` 是内部 AuthZ 服务。它们应该只允许容器网络或可信内网访问。
同理,本地 MinIO 的 `9000/9001``external-connector:8787` 和实例直连端口池 `20000-29999` 也不应该作为正式公网入口。正式入口通常只有:
```text
https://portal.example.com
https://<slug>.apps.example.com
```
外层代理再把它们分别转发到本机 `3081``8088`
### 修改 DEPLOY_PUBLIC_* 后旧实例不会自动改 URL
这些变量影响新创建实例的 `public_url``instance_host`。旧实例已经写入注册表,需要重新创建或手动更新注册表和代理配置。
### 裸 IP 不是通配子域名
如果 `DEPLOY_PUBLIC_BASE_DOMAIN=203.0.113.10`,系统会生成 `http://203.0.113.10:<host_port>` 形式的直连地址,不会生成可用的 `alice.203.0.113.10` 子域名入口。要使用每用户子域名,必须准备真实域名并配置 `*.apps.example.com` 这类通配 DNS。
## 10. 本机测试不需要正式域名
如果只是本机验证完整链路,继续使用:
```bash
export BEAVER_BASE_DOMAIN=localhost
```
它已经足够测试:
- 注册
- 登录
- 自动创建实例
- 跳转个人实例
等准备对外访问时,再切换正式 DNS、HTTPS 和外层代理。