mirror of
https://github.com/Tencent/WeKnora.git
synced 2026-08-29 02:04:30 +08:00
4e3b512af6
IM channels (DingTalk/Feishu) showed broken images because resource://
image references resolve to WeKnora capability links that were neither
reachable nor renderable:
- nginx never proxied /r/, so <APP_EXTERNAL_URL>/r/<token> links fell into
the SPA fallback and returned a blank page. Add a /r/ proxy block
mirroring /files, using the ${APP_SCHEME}/${APP_HOST}/${APP_PORT} vars.
- rewriteStorageURLs treated any "dst != src" as success and emitted
non-HTTP results (e.g. an internal storage:// path when APP_EXTERNAL_URL
is unset) that IM clients cannot fetch. Only substitute http(s) URLs;
otherwise leave the reference unchanged and log an actionable WARN.
- Warn once at startup when IM channels are active but APP_EXTERNAL_URL is
unset — the config state that silently breaks resource:// images on the
default MinIO/local deployment.
- Document that IM knowledge-base images need an IM-reachable http URL via
one of two paths: a publicly reachable storage backend, or APP_EXTERNAL_URL
routing resource:// through nginx /r/.
146 lines
6.3 KiB
Nginx Configuration File
146 lines
6.3 KiB
Nginx Configuration File
# Optional dedicated embed origin — uncomment and set server_name (e.g. embed.example.com).
|
||
# Serve only embed.html + static assets; proxy /api to the WeKnora backend.
|
||
# See docs/embed-subdomain.md
|
||
#
|
||
# server {
|
||
# listen 80;
|
||
# server_name embed.example.com;
|
||
# client_max_body_size ${MAX_FILE_SIZE};
|
||
# location = /weknora-widget.js { root /usr/share/nginx/html; }
|
||
# location ^~ /embed/ { root /usr/share/nginx/html; try_files $uri $uri/ /embed.html; }
|
||
# location ^~ /assets/ { root /usr/share/nginx/html; add_header Cache-Control "public, max-age=31536000, immutable"; }
|
||
# location /api/ { proxy_pass http://weknora-backend:8080; proxy_set_header Host $host; }
|
||
# }
|
||
|
||
server {
|
||
listen 80;
|
||
server_name localhost;
|
||
# Default 50M, configured via MAX_FILE_SIZE_MB env var
|
||
client_max_body_size ${MAX_FILE_SIZE};
|
||
|
||
# 启用 gzip 压缩静态资源 (前端 index.js 单文件 ~1MB, 不压实测 20s+,
|
||
# 压缩后约 200-300KB, 大陆同地域低带宽机器加载从 25s 降到 3-5s)
|
||
gzip on;
|
||
gzip_vary on;
|
||
gzip_min_length 1024;
|
||
gzip_comp_level 6;
|
||
gzip_proxied any;
|
||
gzip_types text/plain text/css text/javascript application/javascript application/json
|
||
application/xml application/rss+xml image/svg+xml font/ttf font/otf;
|
||
|
||
# 安全头配置
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
|
||
# 错误日志配置
|
||
error_log /var/log/nginx/error.log warn;
|
||
access_log /var/log/nginx/access.log;
|
||
|
||
# Static widget loader for third-party sites
|
||
location = /weknora-widget.js {
|
||
root /usr/share/nginx/html;
|
||
add_header Cache-Control "public, max-age=3600" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
}
|
||
|
||
# Internal fallback target for /embed/* routes. Keep it separate from the
|
||
# main SPA location so embed pages do not inherit X-Frame-Options.
|
||
location = /embed.html {
|
||
internal;
|
||
root /usr/share/nginx/html;
|
||
add_header Cache-Control "no-cache, must-revalidate" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# Embed widget pages may be loaded inside third-party iframes (lightweight embed.html)
|
||
location ^~ /embed/ {
|
||
root /usr/share/nginx/html;
|
||
try_files $uri $uri/ /embed.html;
|
||
add_header Cache-Control "no-cache, must-revalidate" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# 前端静态文件
|
||
location / {
|
||
root /usr/share/nginx/html;
|
||
index index.html;
|
||
try_files $uri $uri/ /index.html;
|
||
|
||
# index.html 及 SPA fallback 不可缓存,否则前端升级后用户看不到新版本
|
||
add_header Cache-Control "no-cache, must-revalidate" always;
|
||
# nginx add_header 不从上层继承,需要在 location 内重复声明安全头
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# Vite 构建产物 /assets/* 带 hash 文件名,可长期缓存
|
||
location ^~ /assets/ {
|
||
root /usr/share/nginx/html;
|
||
|
||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
}
|
||
|
||
# 本地存储文件代理到后端服务(用于渲染 markdown 中的图片)
|
||
# 精确匹配 /files,避免 Nginx 自动补 / 触发 301
|
||
location = /files {
|
||
proxy_pass ${APP_SCHEME}://${APP_HOST}:${APP_PORT}/files;
|
||
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;
|
||
}
|
||
|
||
# 资源能力短链 /r/<token>:IM 渠道渲染 resource:// 图片依赖此路由,
|
||
# 缺此段会落进 SPA fallback 返回空白页,图片加载不出来。
|
||
location ^~ /r/ {
|
||
proxy_pass ${APP_SCHEME}://${APP_HOST}:${APP_PORT};
|
||
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;
|
||
}
|
||
|
||
# API请求代理到后端服务
|
||
# APP_SCHEME 默认 http,远程 HTTPS 后端可设为 https
|
||
location /api/ {
|
||
proxy_pass ${APP_SCHEME}://${APP_HOST}:${APP_PORT}/api/;
|
||
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_connect_timeout 30s; # 连接超时时间
|
||
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
|
||
proxy_next_upstream_tries 3; # 重试次数
|
||
proxy_next_upstream_timeout 30s; # 重试超时时间
|
||
|
||
# SSE 相关配置
|
||
proxy_http_version 1.1; # 使用 HTTP/1.1
|
||
proxy_set_header Connection ""; # 禁用 Connection: close,保持连接打开
|
||
chunked_transfer_encoding off; # 关闭分块传输编码
|
||
proxy_buffering off; # 关闭缓冲
|
||
proxy_cache off; # 关闭缓存
|
||
proxy_read_timeout 3600s; # 增加读取超时时间
|
||
proxy_send_timeout 3600s; # 增加发送超时时间
|
||
}
|
||
|
||
# 错误页面
|
||
error_page 500 502 503 504 /50x.html;
|
||
location = /50x.html {
|
||
root /usr/share/nginx/html;
|
||
}
|
||
}
|