跳到主要内容

二级目录部署

二级目录部署是指通过 https://survey.example.com/survey/ 访问 SurveyKing Pro。前端交付包的页面路由、资源路径和接口前缀必须与 Nginx 配置一致。本页使用 public、admin 双入口交付包,后端仍监听 127.0.0.1:48080。

普通前端包不能直接改成二级目录

根目录版本默认使用 /pub/ 和 /adm/。只在 Nginx 前增加 /survey/,或只修改后端的 server.servlet.context-path,都不能完成前端适配。请获取与二级目录匹配的前端交付包;目录名变化时,需要重新交付前端包。

路径约定​

下面以 /survey/ 为例:

内容浏览器或客户端访问路径Nginx 对应目标
系统入口、管理页面/survey/、/survey/minedist/admin/index.html
填写页面/survey/s/问卷ID 等dist/public/index.html
手机扫码预览/survey/preview?key=...dist/admin/index.html
public 构建资源/survey/pub/dist/public/
admin 构建资源/survey/adm/dist/admin/
后端接口/survey/admin-api/后端 /admin-api/
验证码接口/survey/captcha/后端 /captcha/
智能体 MCP / API(按需)/survey/open-api/后端 /open-api/

pub、adm 用于区分构建资源,不是页面路由前缀,填写地址应为 /survey/s/问卷ID,不能写成 /survey/pub/s/问卷ID。

本页由 Nginx 去除 API 的外部 /survey 前缀,后端保持默认上下文路径,不再设置 server.servlet.context-path=/survey。如果交付包约定接口仍走根目录 /admin-api/、/captcha/,应使用与该包一致的代理规则,不能混用本页示例。

部署前检查​

假设前端文件位于:

/usr/local/nginx/html/dist/
├── public/index.html
└── admin/index.html

检查生成的 HTML 是否引用二级目录资源:

grep -o '/survey/pub/' /usr/local/nginx/html/dist/public/index.html | head
grep -o '/survey/adm/' /usr/local/nginx/html/dist/admin/index.html | head

两条命令应分别输出对应的资源前缀。如果没有输出,应先联系交付方核对前端包。HTML 检查只能确认入口资源路径,不能证明登录跳转、分享链接、流式接口和打印地址都已适配,部署后还需完成下文的浏览器验证。

Nginx 生产配置​

下面是站点配置片段,不是完整的 nginx.conf。将域名、二级目录、前端绝对路径和后端地址替换为实际值:

  • map 与 server 放在主配置的 http {} 中,或由 http {} 内的 include 引入;同名 map 只定义一次。
  • 在已有网站中增加二级目录时,将 location 规则合并到该域名现有的 server,保留原网站的其他路径,不另建同域名、同端口的重复站点。
  • 主配置的 http {} 应已引入 mime.types。若其中 JavaScript 映射只有 js,将该行补为 application/javascript js mjs;,使 PDF worker 等 .mjs 文件也返回正确的内容类型。
map $http_upgrade $survey_connection_upgrade {
default upgrade;
'' close;
}

# 仅文件名含内容 hash 的 JS/CSS 长期缓存;logo、PDF worker 等同名文件需重新校验。
map $uri $survey_static_cache_control {
default "no-cache";
"~^/survey/(pub|adm)/.+[.][0-9a-fA-F]{8,}[.](js|css)$" "public, max-age=31536000, immutable";
}

server {
listen 80;
server_name survey.example.com;
index index.html;
client_max_body_size 100m;
# 返回相对重定向,保留外部入口的 HTTPS 和端口。
absolute_redirect off;

location = /survey {
return 301 /survey/$is_args$args;
}

location = /survey/pub {
return 301 /survey/pub/$is_args$args;
}

location = /survey/adm {
return 301 /survey/adm/$is_args$args;
}

# 入口 HTML 不缓存,避免升级后引用旧版资源。
location = /survey/pub/index.html {
root /usr/local/nginx/html/dist/public;
etag off;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

location = /survey/adm/index.html {
root /usr/local/nginx/html/dist/admin;
etag off;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

# 静态资源不存在时直接返回 404,不回退到 SPA HTML。
location ^~ /survey/pub/ {
alias /usr/local/nginx/html/dist/public/;
expires off;
add_header Cache-Control $survey_static_cache_control;
}

location ^~ /survey/adm/ {
alias /usr/local/nginx/html/dist/admin/;
expires off;
add_header Cache-Control $survey_static_cache_control;
}

# 答题、练习、公开查询、数据分享和兑换页面使用 public 入口。
location ~ ^/survey/(e|s|t|q|share|redeem)(/|$) {
root /usr/local/nginx/html/dist/public;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

# 登录、注册和绑定页面使用 public 入口。
location ~ ^/survey/user/(login|register|binding)/?$ {
root /usr/local/nginx/html/dist/public;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}

# 删除外部 /survey 前缀后转发给后端。
location ^~ /survey/captcha/ {
proxy_pass http://127.0.0.1:48080/captcha/;
proxy_set_header Host $http_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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $survey_connection_upgrade;
proxy_read_timeout 1800s;
}

location ^~ /survey/admin-api/ {
proxy_pass http://127.0.0.1:48080/admin-api/;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $http_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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $survey_connection_upgrade;
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
}

# 使用智能体接入时保留:MCP 请求和私有导出下载共用此入口。
location ^~ /survey/open-api/ {
proxy_pass http://127.0.0.1:48080/open-api/;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $http_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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $survey_connection_upgrade;
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
}

# 预览页及其他管理页面使用 admin 入口;这里不要增加 ^~。
location /survey/ {
root /usr/local/nginx/html/dist/admin;
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
try_files /index.html =404;
}
}

配置 HTTPS 时复用同一组 location。不要分别维护内容不同的 HTTP 和 HTTPS 路由。合并到已有站点时,将本页 public 页面正则放在可能冲突的通用正则之前;最后的 location /survey/ 不要改成 location ^~ /survey/,否则会跳过 public 页面正则,错误返回 admin 入口。匹配顺序见 Nginx location 说明。

接口示例中的 location /survey/admin-api/ 与 proxy_pass .../admin-api/ 都保留尾斜杠,例如 /survey/admin-api/system/auth/get-sys-info 会转发为 /admin-api/system/auth/get-sys-info,查询参数继续保留。不要将目标改成只有 /,否则会连同 admin-api 一起去掉。路径替换规则见 Nginx proxy_pass 说明。

上游网关转发​

如果上游是外网入口,并且只允许外网填写,请使用内外网分离部署的白名单方式,并按本页路径约定增加二级目录前缀。下面的整站转发用于可信网关,会转发管理页面与管理 API,不能用于隔离后台。

如果统一门户或总行网关再转发到 SurveyKing Nginx,应保留 /survey/ 前缀。以下例子假设下游 Nginx 监听 192.168.10.20:80,对应上面的 listen 80;如果下游使用其他端口,两处一起修改。此配置添加到网关对应域名的 server 中,并在网关的 http {} 中定义前文的 $survey_connection_upgrade 映射:

location = /survey {
return 301 /survey/$is_args$args;
}

location ^~ /survey/ {
proxy_pass http://192.168.10.20:80;
client_max_body_size 100m;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $http_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_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $survey_connection_upgrade;
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
}

proxy_pass 目标后不要添加 /。添加尾斜杠会删除匹配到的 /survey/ 前缀,导致下游路由失效。

下游 Nginx 已经处理 /survey/admin-api/、/survey/captcha/ 和 /survey/open-api/ 时,上游不需要再为接口创建另一套路径规则。AI 流式响应和长连接需要每一层代理都设置合适的缓冲与超时;WebSocket 的 Upgrade、Connection 也需要逐层转发,见 Nginx WebSocket 代理说明。

如果 HTTPS 在上游网关终止,下游到 Java 的 X-Forwarded-Proto 不能直接用内部连接的 $scheme 覆盖。可在下游 http {} 中增加以下映射,并将下游三个 API 代理中的 proxy_set_header X-Forwarded-Proto $scheme; 改为 proxy_set_header X-Forwarded-Proto $survey_forwarded_proto;:

# 192.168.10.10 替换为下游实际看到的可信网关来源 IP。
map "$remote_addr:$http_x_forwarded_proto" $survey_forwarded_proto {
default $scheme;
"192.168.10.10:https" https;
"192.168.10.10:http" http;
}

验证部署​

nginx -t && systemctl reload nginx

# --resolve 同时指定站点域名与本机 IP,避免命中其他默认站点。
curl -I --resolve survey.example.com:80:127.0.0.1 http://survey.example.com/survey/
curl -I --resolve survey.example.com:80:127.0.0.1 http://survey.example.com/survey/pub/index.html
curl -I --resolve survey.example.com:80:127.0.0.1 http://survey.example.com/survey/adm/index.html

# 填写页返回 public 入口,预览页返回 admin 入口。
curl -fsS --resolve survey.example.com:80:127.0.0.1 http://survey.example.com/survey/s/route-check \
| grep -o '/survey/pub/' | head
curl -fsS --resolve survey.example.com:80:127.0.0.1 http://survey.example.com/survey/preview \
| grep -o '/survey/adm/' | head

# 应返回 JSON,不应返回 index.html。
curl -i --resolve survey.example.com:80:127.0.0.1 \
http://survey.example.com/survey/admin-api/system/auth/get-sys-info

# 不存在的资源应返回 404,不应返回 HTTP 200 的 SPA HTML。
curl -I --resolve survey.example.com:80:127.0.0.1 \
http://survey.example.com/survey/pub/__missing_route_check__.js

将命令中的域名、端口和 IP 替换为实际值。经 HTTPS 网关访问时,另用正式 HTTPS 地址验证;不能只验证下游 Nginx。

两个 index.html 应返回 HTTP 200 和禁止缓存的响应头。route-check 是用于确认页面回退的占位 ID,只能验证 Nginx 返回了正确的 HTML,实际填写、预览仍需有效的项目 ID 或预览 key。仅看到 HTTP 200 不能证明前端入口和 API 配置正确。

二级目录和根目录的路由归属相同:e、s、t、q、share、redeem 使用 public 入口;/survey/user/login、/survey/user/register、/survey/user/binding 也使用 public 入口;/survey/preview 和管理页面使用 admin 入口。

浏览器中继续验证:

  • 登录和退出后仍停留在 /survey/ 下。
  • 管理后台、填写页和手机扫码预览页刷新不会返回 404,且加载对应的 /survey/adm/ 或 /survey/pub/ 资源。
  • 问卷、考试、练习、公开查询和分享链接都包含 /survey/。
  • 普通 API、验证码、AI 流式响应和考试监考请求都使用约定的接口前缀,返回 JSON 或事件流,不是 HTML。
  • 上传、图片预览、文件下载、报表打印,以及邮件、短信和嵌入代码中的链接路径正确。

如果只有部分功能请求根目录 /admin-api/、/captcha/,或链接缺少 /survey/,说明交付包中的相关请求或链接尚未适配。应由交付方修正对应包,不能仅靠增加页面回退解决。

系统初始化​

部署完成后登录管理后台完成系统初始化,再正式开放使用。

系统地址与分享链接​

在 基础设施 → 配置管理 → 界面配置 → 系统地址 设置用户实际访问的域名。本例中,由前端追加 /survey/ 的交付包填写 https://survey.example.com,不再重复填写 /survey;非标准端口需一并填写。不同交付包以其链接拼接约定为准。

保存并刷新后,进入项目分享页面,确认链接类似 https://survey.example.com/survey/s/问卷ID,且 /survey 只出现一次。该设置不会修改已保存的文件地址或第三方登录回调地址。

文件存储​

在 基础设施 → 文件管理 → 文件配置 设置文件存储,页面路径为 /survey/infra/file/file-config。使用本地硬盘时,选择“本地磁盘”,将“基础路径”设为后端运行用户可读写的绝对目录;文件对外地址需要能通过实际域名和代理路径访问。

不能只修改系统地址就认为历史附件已适配。应分别验证新上传和历史图片、附件的预览、下载;对象存储或独立文件域名按自身地址访问,不机械追加 /survey。

智能体接入(按需)​

使用支持 MCP 的版本时,保留 /survey/open-api/ 代理,并在 配置管理 → 智能体接入 中将“对外站点地址”明确填写为 https://survey.example.com/survey。该配置由后端直接拼接接口路径,与前端系统地址的拼接方式不同;不要在此场景下留空回退到不含前缀的系统地址。

“远程 MCP 地址”可留空,默认得到 https://survey.example.com/survey/open-api/agent/mcp。保存后核对连接地址及导出下载 URL,二者都应包含 /survey/open-api/。

常见问题​

页面能打开,但 JavaScript 和 CSS 返回 404​

先看失败请求的路径:若仍引用根目录 /pub/ 或 /adm/,请联系交付方获取与二级目录匹配的包;若已包含 /survey/,核对 alias 目录、文件是否完整及 Nginx 读取权限。

PDF 预览提示模块类型错误​

检查 .mjs 请求是否返回 JavaScript 内容类型。若是 application/octet-stream,补齐 mime.types 中的 mjs 映射;若是 text/html,先排查是否返回了错误的 SPA 入口。

登录后跳回域名根目录​

检查前端页面路由前缀、登录后跳转地址和第三方平台登记的完整回调 URL 是否匹配当前部署。平台只要求填写“域名”时按其字段要求填写域名,不要把 /survey/ 当成域名的一部分;完整回调 URL 则要保留对应路径。

接口请求出现双重 /survey/survey/​

检查上游 proxy_pass 是否重复添加路径,以及前端 API 前缀是否已经包含 /survey。如果重复发生在分享链接中,同时检查系统地址与前端路由前缀是否被重复拼接。

接口返回 HTTP 200,但页面提示解析失败​

检查响应的 Content-Type 和正文。若返回 text/html 或 <!doctype html>,请求可能落到了 SPA 页面回退。核对 API 的请求前缀与代理规则,不要把 HTML 的 HTTP 200 当成接口成功。

经过统一门户后 AI 一次性出完、监考连接断开或上传失败​

确认每层代理都关闭了接口响应缓冲、设置了合适的读写超时,并允许 WebSocket 升级。上传返回 413 时,核对每层代理的 client_max_body_size 和后端上传限制;只修改最内层 Nginx 不会解除外层网关限制。

修改 Nginx 后仍显示旧页面​

确认 index.html 禁止缓存,并清理浏览器、CDN 或统一门户缓存。本页只对带内容 hash 的 JS/CSS 设置长期缓存;同名的 logo.png、favicon.ico、PDF worker 等文件仍需重新校验,不能对整个资源目录统一设置 immutable。