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: 添加技能学习和工具执行的单元测试 增加测试用例验证并发草案合成、重复外部写入抑制和无变化修订检测等功能。 ```
9.7 KiB
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,再转发到本机端口:
portal.example.com -> 127.0.0.1:3081
*.apps.example.com -> 127.0.0.1:8088
2. 推荐域名规划
推荐拆成两个域名空间:
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 记录:
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
最小映射:
portal.example.com -> http://127.0.0.1:3081
*.apps.example.com -> http://127.0.0.1:8088
*.apps.example.com 转发到 router-proxy 时必须保留原始 Host,例如:
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 基域名直连模式使用,控制实例宿主机端口绑定地址 |
本机测试:
export BEAVER_BASE_DOMAIN=localhost
deploy-control:
-e DEPLOY_PUBLIC_SCHEME="http" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="$BEAVER_BASE_DOMAIN" \
-e DEPLOY_PUBLIC_PORT="8088" \
生成实例地址:
http://alice.localhost:8088
正式 HTTPS:
export BEAVER_BASE_DOMAIN=apps.example.com
deploy-control:
-e DEPLOY_PUBLIC_SCHEME="https" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="apps.example.com" \
-e DEPLOY_PUBLIC_PORT="443" \
生成实例地址:
https://alice.apps.example.com
前提是外层代理已经把 *.apps.example.com:443 转发到 router-proxy:8088。
裸 IP 特例:
export BEAVER_BASE_DOMAIN=203.0.113.10
当 DEPLOY_PUBLIC_BASE_DOMAIN 是 IP 地址时,deploy-control 会进入直连端口模式:每个实例从 20000-29999 端口池分配一个宿主机端口,生成类似:
http://203.0.113.10:20037
这不是 router-proxy 的 Host 路由入口,也无法得到 https://alice.apps.example.com 这类实例子域名。正式环境推荐使用 apps.example.com 这类真实域名和通配 DNS。
6. 什么时候 URL 里可以不带端口
浏览器默认端口:
http默认80https默认443
所以:
DEPLOY_PUBLIC_SCHEME=https
DEPLOY_PUBLIC_PORT=443
生成:
https://alice.apps.example.com
而不是:
https://alice.apps.example.com:443
如果你设置:
DEPLOY_PUBLIC_SCHEME=http
DEPLOY_PUBLIC_PORT=8088
生成:
http://alice.apps.example.com:8088
因为 8088 不是 HTTP 默认端口。
7. 一套推荐生产配置
假设:
- 门户域名:
portal.example.com - 实例基域名:
apps.example.com - 外层代理负责 HTTPS
- 项目基础容器仍在同一台机器上通过 Docker 运行
部署变量:
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:
-e DEPLOY_PUBLIC_SCHEME="https" \
-e DEPLOY_PUBLIC_BASE_DOMAIN="apps.example.com" \
-e DEPLOY_PUBLIC_PORT="443" \
外层代理:
portal.example.com -> 127.0.0.1:3081
*.apps.example.com -> 127.0.0.1:8088
DNS:
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 外层代理示例
示例只展示关键转发逻辑,证书路径和自动签发方式按你的环境调整。
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 只能做:
域名 -> IP
端口来自:
- URL 里显式端口
- 协议默认端口
- 外层代理监听和转发规则
不要把 portal 转到 8088
8088 是实例入口,不是认证门户入口。
推荐:
portal.example.com -> 3081
*.apps.example.com -> 8088
不要公开内部端口
8090 是部署控制面,19090 是内部 AuthZ 服务。它们应该只允许容器网络或可信内网访问。
同理,本地 MinIO 的 9000/9001、external-connector:8787 和实例直连端口池 20000-29999 也不应该作为正式公网入口。正式入口通常只有:
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. 本机测试不需要正式域名
如果只是本机验证完整链路,继续使用:
export BEAVER_BASE_DOMAIN=localhost
它已经足够测试:
- 注册
- 登录
- 自动创建实例
- 跳转个人实例
等准备对外访问时,再切换正式 DNS、HTTPS 和外层代理。