跳到主要内容

内外网分离部署

管理员在内网设计问卷、查看答卷,填写者通过外网链接作答。要实现这套流程,需要分别做好两件事:由 Nginx 限制外网可以访问的页面与接口,再用“系统地址”生成正确的外网发布链接和二维码。

本文承接基础软件环境安装和标准部署,采用当前 public / admin 两套前端交付包的根目录部署方式。示例覆盖普通匿名问卷、常用填写字段、验证码,以及当前项目目录中的图片和答题附件。登录、360、考试结果等扩展功能见后面的按需开放规则。

有两台 Nginx 时,按步骤 1、2 配置内外网两层代理。只有一台 Nginx 时,直接使用一个配置文件的完整示例,再继续步骤 3 设置系统地址。

访问方式与网络边界​

配置项本文示例用途
内网管理地址http://admin.example.internal管理员访问设计、数据与系统设置
外网填写地址https://survey.example.com供填写者打开问卷
外网网关的内网 IP10.24.10.10外网 Nginx 连接内网服务器时的来源地址
内网服务器地址10.24.10.24:80提供前端文件,并将 API 转发到 Java 后端
管理员内网网段10.24.20.0/24允许直接访问内网 Nginx 的办公网络
Java 后端127.0.0.1:48080运行在内网服务器上
前端目录/opt/surveyking/client/dist包含 public/ 与 admin/ 两个目录

这些域名和 IP 均为示例,请替换为实际值。内网域名只在内部 DNS 解析;外网域名解析到外网网关,并配置有效的 HTTPS 证书。管理员如果需要通过 HTTPS 访问内网,也应为内网站点配置对应证书。

防火墙只允许办公网段和外网网关访问内网 Nginx;Java、数据库和 Redis 不做公网端口映射。外网网关不应存在另一个可以直达内网管理站点的通配转发入口。仅隐藏后台链接或不公开内网域名,不能代替这些限制。

1. 配置内网 Nginx​

内网保留完整的管理功能。先确认以下文件存在:

/opt/surveyking/client/dist/
├── public/index.html
└── admin/index.html

下面是内网服务器上的完整 nginx.conf。修改管理员网段、网关来源 IP、前端目录和后端端口;有 NAT 时,allow 和协议判断中的地址应使用内网服务器实际收到的来源 IP。

下载内网完整配置

内网 /etc/nginx/nginx.conf
# 内网服务器上的完整 nginx.conf。替换域名、网段和目录后使用。
# 同一示例地址或目录出现多次时,请同步替换,保持一致。
worker_processes auto;
# pid 和日志目录如与本机 Nginx 安装位置不同,请按实际路径修改。
pid /var/run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events {
worker_connections 1024;
}

http {
# 改为本机 Nginx 的 mime.types 文件路径。
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
sendfile on;

# 仅接受指定外网网关传入的协议,内网直接访问使用自身协议。
map "$remote_addr:$http_x_forwarded_proto" $sk_original_scheme {
default $scheme;
# 两处 IP 均改为外网网关的实际来源 IP,与下方 allow 保持一致。
# 有 NAT 时,填写内网 Nginx 实际收到的来源 IP。
"10.24.10.10:https" https;
"10.24.10.10:http" http;
}

upstream surveyking_backend {
# 改为 Java 后端的实际 IP 和端口;此例为同机部署、端口 48080。
server 127.0.0.1:48080;
}

server {
# 内网 Nginx 的监听端口;修改后同步更新外网 upstream 中的端口。
listen 80 default_server;
# 分别改为实际的内网管理域名、外网填写域名,不带协议或路径。
server_name admin.example.internal survey.example.com;
client_max_body_size 100m;
index index.html;

# 改为外网网关的实际来源 IP,与上方 map 一致;有 NAT 时使用转换后的 IP。
allow 10.24.10.10;
# 改为允许访问后台的管理员办公网段,保留正确的 CIDR 掩码。
allow 10.24.20.0/24;
allow 127.0.0.1;
deny all;

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 $sk_original_scheme;
proxy_set_header Connection "";
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
proxy_cache off;

location = /pub/index.html {
# 改为 public 构建文件的实际目录,所有 public 路径同步修改。
root /opt/surveyking/client/dist/public;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location = /adm/index.html {
# 改为 admin 构建文件的实际目录,所有 admin 路径同步修改。
root /opt/surveyking/client/dist/admin;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location /pub/ {
# 改为实际 public 目录,保留末尾斜杠。
alias /opt/surveyking/client/dist/public/;
}

location /adm/ {
# 改为实际 admin 目录,保留末尾斜杠。
alias /opt/surveyking/client/dist/admin/;
}

location = /favicon.ico {
# 改为实际 public 目录,与上方 public 路径一致。
root /opt/surveyking/client/dist/public;
try_files /favicon.ico =404;
}

location ~ ^/(s|e|t|q|share|redeem)(/|$) {
# 改为实际 public 目录,与上方 public 路径一致。
root /opt/surveyking/client/dist/public;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location ~ ^/user/(login|register|binding)/?$ {
# 改为实际 public 目录,与上方 public 路径一致。
root /opt/surveyking/client/dist/public;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location /admin-api/ {
# surveyking_backend 对应上方 upstream 名称,后端地址在 upstream 中修改。
proxy_pass http://surveyking_backend;
proxy_buffering off;
}

location /captcha/ {
# 与上方 Java 后端的 upstream 名称保持一致。
proxy_pass http://surveyking_backend;
}

# 管理页面只在内网使用,外网网关不会放行这些路径。
location / {
# 改为实际 admin 目录,与上方 admin 路径一致。
root /opt/surveyking/client/dist/admin;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}
}
}

这里没有信任任意来源的 X-Forwarded-For 来改变访问控制判断。外网网关在下一步覆盖客户端自带的转发头,内网再将这条代理链传给后端。

2. 配置外网 Nginx​

外网配置将 /admin-api/survey/ 下的全部接口统一放行,不需要逐个维护加载、提交、暂存、上传等接口名称。填写页面、静态资源和少量公共接口分别放行,其余路径默认拒绝。

这条前缀规则也包含以后新增在 /admin-api/survey/ 下的接口;具体请求方法、参与权限和数据权限继续由后端校验。项目管理、答卷管理等其他接口仍保持隔离。

基线放行内容为什么需要
GET/HEAD /s/:id打开、刷新问卷填写页
GET/HEAD /pub/ 下指定类型的静态资源加载答题端 JavaScript、CSS、图片与字体
GET /admin-api/system/auth/get-sys-info初始化答题端的系统名称、主题等基础信息
/admin-api/survey/ 下的全部接口统一覆盖加载问卷、校验、提交、暂存、关联回填与上传等答题功能
验证码接口获取并校验填写时的人机验证
指定项目目录的文件读取接口读取题目素材和带访问凭证的答题附件

基线不会开放后台首页、/project/、/infra/、/adm/、登录注册页面,以及项目管理、答卷管理、文件管理和系统管理 API。/admin-api/survey/saveAnswer 是填写者提交答案的入口;/admin-api/answer/* 则是管理端接口,不能因为名称含有“答案”就全部放行。

下面是外网网关上的完整 nginx.conf。将 survey.example.com、证书路径和上游 10.24.10.24:80 替换为实际值。网关本身不需要存放管理端前端文件。

下载外网完整配置

外网 /etc/nginx/nginx.conf
# 外网网关上的完整 nginx.conf。替换域名、证书和上游地址后使用。
# 基线:匿名问卷、常用答题字段与项目图片/附件;其他功能按文档扩展。
# 同一示例域名出现多次时,请同步替换为实际的外网填写域名。
worker_processes auto;
# pid 和日志目录如与本机 Nginx 安装位置不同,请按实际路径修改。
pid /var/run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events {
worker_connections 1024;
}

http {
# 改为本机 Nginx 的 mime.types 文件路径。
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
# 为按需增加的登录权限等较长的白名单键预留空间。
map_hash_bucket_size 128;

# 不记录查询参数,避免在网关访问日志中保存填写链接或附件凭证。
log_format survey_gateway '$remote_addr [$time_local] '
'$request_method $uri $status $body_bytes_sent';
access_log /var/log/nginx/access.log survey_gateway;

upstream surveyking_intranet {
# 改为内网 Nginx 的实际 IP(或内网域名)和监听端口。
# 此例为 10.24.10.24:80,端口须与内网配置的 listen 一致。
server 10.24.10.24:80;
}

# 使用规范化路径 $uri 判断,查询参数不参与路由匹配。
# survey 答题接口统一放行;其他路径默认拒绝。
map "$request_method:$uri" $sk_public_allowed {
default 0;

# 普通问卷填写页。结果页、360、练习、快查按文档单独增加。
~^(GET|HEAD):/s/[A-Za-z0-9_-]+/?$ 1;

# 只提供 public 构建资源,不提供 admin 构建或源码映射文件。
~^(GET|HEAD):/pub/.+\.(js|mjs|css|png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|otf|eot|wasm|json)$ 1;
~^(GET|HEAD):/favicon\.ico$ 1;

# public 入口启动时读取的系统基础信息。
"GET:/admin-api/system/auth/get-sys-info" 1;

# 统一放行 survey 下的全部接口,无需逐个维护接口名称。
# 请求方法、参与权限与数据权限继续由后端校验。
~^[A-Z]+:/admin-api/survey/ 1;

# 位于 survey 前缀之外的滑动验证码接口。
"POST:/captcha/get" 1;
"POST:/captcha/check" 1;

# 当前版本的项目素材与受保护答题附件读取入口。
# 私有附件仍需后端校验 attachmentToken,不能用 alias 绕过。
~^(GET|HEAD):/admin-api/infra/file/[0-9]+/get/project/[A-Za-z0-9_-]+/(editor|answer-private)/.+$ 1;
}

# 非指定域名的 HTTP 请求不会转发。
server {
listen 80 default_server;
server_name _;
return 404;
}

server {
listen 80;
# 改为实际外网填写域名,与下方 HTTPS 配置、Host 校验保持一致。
server_name survey.example.com;
if ($sk_public_allowed = 0) { return 404; }
# 改为实际外网 HTTPS 地址,保留 $request_uri;非 443 端口需在域名后注明。
return 308 https://survey.example.com$request_uri;
}

server {
# HTTPS 监听端口;如改为非 443,系统地址和上方重定向地址也需带端口。
listen 443 ssl default_server;
# 改为同一个外网填写域名,不带协议或端口。
server_name survey.example.com;
# 改为该外网域名对应的完整证书链文件路径。
ssl_certificate /etc/nginx/certs/survey.example.com/fullchain.pem;
# 改为与上述证书配套的私钥文件路径。
ssl_certificate_key /etc/nginx/certs/survey.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 100m;

# 即使伪造内网域名,也不能经此网关访问后台。
# 此处也要改为实际外网填写域名,与 server_name 一致。
if ($host != survey.example.com) { return 404; }

# 本站只有这一处转发入口;不添加其他可绕过白名单的 location。
location / {
if ($sk_public_allowed = 0) { return 404; }

# 不附加 URI 或尾斜杠,保留请求路径和原有查询参数。
# surveyking_intranet 对应上方 upstream 名称,内网地址在 upstream 中修改。
proxy_pass http://surveyking_intranet;
proxy_http_version 1.1;
# 改为实际外网填写域名,与 server_name、Host 校验保持一致。
proxy_set_header Host survey.example.com;
proxy_set_header X-Real-IP $remote_addr;
# 外网边界覆盖客户端自带的转发头。
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
proxy_buffering off;
proxy_cache off;
}
}
}

map 位于 http 中;$uri 用于匹配路径,不受填写链接上的查询参数影响。唯一的 location / 先检查白名单再转发,不允许额外增加一个直接代理 /admin-api/ 的入口。Nginx 的匹配机制见官方 map 文档。

此处 proxy_pass 不附加路径,保留原请求路径和查询参数,例如渠道参数、填写凭证、附件访问凭证。不要随意在上游地址后追加 / 或改成固定路径,相关行为见 Nginx proxy_pass 说明。

上述文件是主配置,不是可直接叠加的 server 片段。已有 Nginx 实例应先备份配置,再将所需内容合并到相应层级;检查并移除该站点旧的宽泛转发规则,避免另一监听端口或站点绕过白名单。不要覆盖同一实例上其他业务的站点。

图片、附件和旧文件路径​

当前配置只放行文件读取中的 project/<项目 ID>/editor/ 和 project/<项目 ID>/answer-private/ 路径。它不会开放 /admin-api/infra/file/upload、page 或 delete 等管理功能。

在 基础设施 → 文件管理 → 文件配置 中检查文件服务域名:返回给填写者的图片和附件 URL 必须能从外网访问。可以使用上述外网域名,也可以使用独立的文件服务域名。修改“系统地址”不会自动替换已保存的图片、附件地址或对象存储域名。

旧版本文件、系统 Logo、公共素材可能使用不同目录。先在浏览器网络面板确认真实 URL,再按实际配置编号与目录补充只读规则。例如,确认文件属于配置 1 的公开素材目录后,只开放相应前缀;不要为了修复一张图片而开放整个 /infra/ 或 /admin-api/。

答题私有附件还需要后端验证 URL 中的短期 attachmentToken。代理必须保留查询参数,并继续转发给后端;不能用 Nginx alias 直接映射私有文件目录,否则会绕过凭证检查。详情见文件存储。

同一台 Nginx:使用一个配置文件​

可以在同一个 nginx.conf 的 http 中配置多个 server,分别接收内网管理和外网填写请求。前端文件放在同一台服务器,两个站点共用 Java 后端。

入口地址访问规则
内网管理
http://admin.example.internal
仅允许管理员办公网段访问,保留完整管理功能
外网填写
https://survey.example.com
放行填写页面、静态资源、/admin-api/survey/ 和必要公共接口,其余拒绝

将内网域名解析到这台 Nginx 的内网 IP,外网域名解析到它的公网 IP 或对应的公网映射地址。这里用域名区分站点,同时用管理站点的 allow / deny 限制来源;外网即使伪造内网域名,也应被来源网段规则拒绝。域名选站和来源地址限制的行为可查阅 Nginx 请求处理说明及访问控制说明。

下面这一份文件即可完成配置。upstream surveyking_backend 填写 Java 后端的实际 IP 和端口,例如同机部署的 127.0.0.1:48080。此方案由 Nginx 直接提供前端资源并代理后端,不需要再转发到另一台内网 Nginx。

下载单台 Nginx 完整配置

单台 Nginx /etc/nginx/nginx.conf
# 同一台 Nginx 的完整 nginx.conf:内网管理、外网填写,共用 Java 后端。
# 同一示例域名或目录出现多次时,请同步替换,保持一致。
worker_processes auto;
# pid 和日志目录如与本机 Nginx 安装位置不同,请按实际路径修改。
pid /var/run/nginx.pid;
error_log /var/log/nginx/error.log warn;

events {
worker_connections 1024;
}

http {
# 改为本机 Nginx 的 mime.types 文件路径。
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;
sendfile on;
map_hash_bucket_size 128;
client_max_body_size 100m;

# 不记录查询参数,避免保存填写链接或附件凭证。
log_format survey_gateway '$remote_addr [$time_local] '
'$request_method $uri $status $body_bytes_sent';
access_log /var/log/nginx/access.log survey_gateway;

upstream surveyking_backend {
# 改为 Java 后端的实际 IP 和端口;此例为同机部署、端口 48080。
# 此单文件方案直接连接 Java 后端,此处不要填写 Nginx 自身的监听端口。
server 127.0.0.1:48080;
}

# 仅用于外网填写站点,管理站点不使用这份路径白名单。
map "$request_method:$uri" $sk_public_allowed {
default 0;

# 填写页面与 public 静态资源;结果页、360、练习等按文档扩展。
~^(GET|HEAD):/s/[A-Za-z0-9_-]+/?$ 1;
~^(GET|HEAD):/pub/.+\.(js|mjs|css|png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|otf|eot|wasm|json)$ 1;
~^(GET|HEAD):/favicon\.ico$ 1;

# 答题端初始化,以及 survey 前缀下的全部接口。
"GET:/admin-api/system/auth/get-sys-info" 1;
~^[A-Z]+:/admin-api/survey/ 1;

# 位于 survey 前缀之外的滑动验证码接口。
"POST:/captcha/get" 1;
"POST:/captcha/check" 1;

# 项目素材与受保护答题附件;私有附件仍由后端校验 attachmentToken。
~^(GET|HEAD):/admin-api/infra/file/[0-9]+/get/project/[A-Za-z0-9_-]+/(editor|answer-private)/.+$ 1;
}

# 所有 API 代理共用这些设置;本例由客户端直接连接此 Nginx。
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 覆盖客户端自带的转发头,访问控制始终按实际来源地址判断。
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
proxy_connect_timeout 60s;
proxy_read_timeout 1800s;
proxy_send_timeout 1800s;
proxy_buffering off;
proxy_cache off;

# 未匹配到配置域名的 HTTP 请求直接拒绝。
server {
listen 80 default_server;
server_name _;
return 404;
}

# 内网管理站点:完整管理功能,且所有 location 都继承来源地址限制。
server {
listen 80;
# 改为实际内网管理域名,只在内网 DNS 中解析到本机内网 IP。
server_name admin.example.internal;

# 改为实际管理员办公网段;不要把公网代理或 NAT 网关的地址当成办公网段。
allow 10.24.20.0/24;
allow 127.0.0.1;
deny all;

# 改为 admin 构建文件的实际目录,下方 admin 路径同步修改。
root /opt/surveyking/client/dist/admin;

location = /pub/index.html {
# 改为 public 构建文件的实际目录,下方 public 路径同步修改。
root /opt/surveyking/client/dist/public;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location = /adm/index.html {
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location /pub/ {
# 改为实际 public 目录,保留末尾斜杠。
alias /opt/surveyking/client/dist/public/;
}

location /adm/ {
# 改为实际 admin 目录,保留末尾斜杠。
alias /opt/surveyking/client/dist/admin/;
}

location = /favicon.ico {
# 改为实际 public 目录,与其他 public 路径一致。
root /opt/surveyking/client/dist/public;
try_files /favicon.ico =404;
}

location ~ ^/(s|e|t|q|share|redeem)(/|$) {
# 改为实际 public 目录,与其他 public 路径一致。
root /opt/surveyking/client/dist/public;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location ~ ^/user/(login|register|binding)/?$ {
# 改为实际 public 目录,与其他 public 路径一致。
root /opt/surveyking/client/dist/public;
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}

location /admin-api/ {
# 后端 IP 和端口在上方 surveyking_backend 中修改,保留完整请求路径。
proxy_pass http://surveyking_backend;
}

location /captcha/ {
proxy_pass http://surveyking_backend;
}

location / {
# 使用本 server 上方配置的 admin 目录。
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}
}

# 外网 HTTP 入口:只将已放行的路径跳转到 HTTPS。
server {
listen 80;
# 改为实际外网填写域名,与下方 HTTPS 站点和 Host 校验保持一致。
server_name survey.example.com;
if ($sk_public_allowed = 0) { return 404; }
# 改为实际外网 HTTPS 地址,保留 $request_uri;非 443 端口需在域名后注明。
return 308 https://survey.example.com$request_uri;
}

# 外网 HTTPS 入口:所有页面、静态资源和 API 都先经过白名单。
server {
# 如改为非 443 端口,系统地址和上方重定向地址也需带端口。
listen 443 ssl default_server;
# 改为同一个外网填写域名,不带协议或端口。
server_name survey.example.com;
# 改为该外网域名对应的完整证书链和私钥文件路径。
ssl_certificate /etc/nginx/certs/survey.example.com/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/survey.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;

# 此处也要改为实际外网填写域名,与 server_name 一致。
if ($host != survey.example.com) { return 404; }
# 在 server 级统一检查,覆盖下方每个 location。
if ($sk_public_allowed = 0) { return 404; }

# 改为实际 public 目录,与内网站点的 public 路径一致。
root /opt/surveyking/client/dist/public;

location /pub/ {
# 改为实际 public 目录,保留末尾斜杠。
alias /opt/surveyking/client/dist/public/;
}

location = /favicon.ico {
try_files /favicon.ico =404;
}

location /admin-api/ {
# 只转发已通过上方白名单的 API;后端地址在 upstream 中修改。
proxy_pass http://surveyking_backend;
}

location /captcha/ {
proxy_pass http://surveyking_backend;
}

location / {
# 已放行的页面均返回 public 入口,不提供 admin 构建文件。
add_header Cache-Control "no-store" always;
try_files /index.html =404;
}
}
}

使用时逐项修改域名、证书、办公网段、前端目录和 Java 后端地址。文件只保留一套 events、http,不要将前面两份主配置直接首尾拼接。登录、结果页、360 等按需规则统一加到这份配置的 map 中。

本例中客户端直接连接 Nginx,管理权限按它收到的来源 IP 判断。前面还有负载均衡、CDN 或其他代理时,需要先按可信代理配置恢复真实来源 IP,再核对办公网段;不要直接将代理地址加入办公网白名单。Java、数据库和 Redis 仍不开放公网端口。内网管理入口沿用 HTTP 示例,需要内网 HTTPS 时,为管理域名配置相应证书,并保留同样的来源限制。

单文件方案额外验证​

除后面的填写、提交和文件验证外,再检查管理站点的来源限制。将域名和 Nginx公网IP 替换为实际值:

# 在办公网内执行:正常打开管理页面,返回 admin 入口 HTML。
curl -I 'http://admin.example.internal/project/实际问卷ID/edit'

# 在办公网以外执行:即使请求指定内网域名,也应返回 403。
curl -i --resolve 'admin.example.internal:80:Nginx公网IP' \
'http://admin.example.internal/project/实际问卷ID/edit'
curl -i --resolve 'admin.example.internal:80:Nginx公网IP' \
'http://admin.example.internal/admin-api/project/page'

# 外网填写域名下的管理路径仍应返回 404。
curl -I 'https://survey.example.com/project/实际问卷ID/edit'

发布链接仍需按下一节配置“系统地址”为 https://survey.example.com;使用单个配置文件不会自动改变链接生成地址。

3. 配置系统地址,让发布链接指向外网​

未配置时为什么显示内网地址​

未配置“系统地址”时,发布链接使用当前浏览器访问地址的协议、主机和端口,再拼接 /s/<问卷 ID>。例如管理员从 http://admin.example.internal 进入系统,生成的链接默认也是这个内网域名。

下面是当前 Chrome 页面中的实际演示:管理员从本机 localhost:8001 设计问卷,未配置时发布链接也显示 localhost:8001。

未配置系统地址时,发放问卷面板的发布链接为当前访问地址 localhost:8001

在管理后台设置外网地址​

  1. 在内网登录管理后台,进入 基础设施 → 配置管理 → 界面配置。
  2. 找到 系统地址,点击右下角编辑图标。
  3. 根目录部署填写实际的外网地址,例如 https://survey.example.com,然后保存。这里不填写 /admin-api、/pub 或具体的 /s/问卷ID。
  4. 刷新已打开的问卷设计页面,使其重新读取系统配置。

在基础设施、配置管理、界面配置中,将系统地址保存为示例外网域名

截图使用示例域名 https://survey.example.com 演示已保存状态;实际部署须填写已经完成 DNS、HTTPS 与网关配置的真实域名。

核对发布链接和二维码​

返回问卷,点击右上角 发放问卷。配置后,同一个问卷的发布链接变为 https://survey.example.com/s/Srfpdx,二维码也由这一地址生成;管理员可以继续在内网设计问卷。

保存系统地址并刷新后,同一问卷的发布链接和二维码已使用 survey.example.com 外网域名

两张发布截图来自同一问卷,只有系统地址不同。示例问卷处于“未开启”状态,用于展示链接;实际收集前还需要完成发布检查并开启问卷。

“系统地址”负责生成链接,不负责建立 DNS、签发证书或隔离管理端。已复制到群聊的旧链接、已下载的旧二维码不会被自动更新,需要重新复制和下载。

4. 按项目功能扩展白名单​

/admin-api/survey/ 下的接口已经全部放行,扩展功能时主要补充页面路由,以及位于该前缀之外的接口。下面的代码按需放入外网配置现有的 map 中,保留 default 0。

必须登录才能填写​

账号密码登录与管理端共用认证服务。确实需要登录答题时,可以增加以下最小规则:

~^(GET|HEAD):/user/login/?$ 1;
"POST:/admin-api/system/auth/login" 1;
"POST:/admin-api/system/auth/logout" 1;
"POST:/admin-api/system/auth/refresh-token" 1;
"GET:/admin-api/system/auth/get-permission-info" 1;

这会开放共享登录入口,不能仅凭 Nginx 路径区分“填写者登录”和“管理员登录”。后台页面和管理 API 仍由白名单拒绝,项目参与范围与数据权限由后端校验。如果要求外网完全不出现账号登录入口,就不要添加这组规则,也不要在该问卷中启用“需要用户登录”。

短信、微信、企业微信、钉钉、OAuth2 等登录方式使用各自的接口与回调。根据所选方式补充精确路径,同时在第三方平台登记外网回调地址;不要整体放行 /admin-api/system/auth/。

提交后结果、360 评估或练习​

这些功能的 survey 接口已经放行,只需根据使用情况增加对应的页面路由:

# 查看某次答卷,以及考试结果和维度结果。
~^(GET|HEAD):/s/[A-Za-z0-9_-]+/[A-Za-z0-9_-]+/?$ 1;
~^(GET|HEAD):/s/[A-Za-z0-9_-]+/(exam-result|dimension)/[A-Za-z0-9_-]+/?$ 1;

# 360 评价入口;项目要求登录时还需添加登录规则。
~^(GET|HEAD):/e/[A-Za-z0-9_-]+/?$ 1;

# 练习与练习结果;需要账号的流程还需添加登录规则。
~^(GET|HEAD):/t/[A-Za-z0-9_-]+(/[A-Za-z0-9_-]+)?/?$ 1;

快查、数据分享、抽奖、红包、课程中心或模板打印的页面需要另行开放,路由可参见前端路由归属。这些功能若调用 /admin-api/survey/ 之外的接口,也需要补充相应规则;上线时按浏览器实际请求核对。

考试监考的 /admin-api/survey/proctoring/ 接口已包含在前缀规则中,无需重复添加。现有代理配置的 proxy_buffering off 支持命令流及时到达。

使用 /survey/ 等二级目录​

本文的两层代理和单台 Nginx 配置都用于根目录部署。二级目录需要先使用二级目录部署对应的前端交付包和站点配置,再将外网白名单中的页面、静态资源与 API 路径一起调整为实际前缀,例如 /survey/s/...、/survey/pub/...、/survey/admin-api/...。上游转发仍应保留完整路径。

发布链接最终必须类似 https://survey.example.com/survey/s/问卷ID,且 /survey 只出现一次。按所用交付包核对系统地址与前端路由前缀的拼接结果;页面和接口规则都要同步调整,并继续保持管理接口隔离。

5. 上线验证​

在使用的 Nginx 服务器上检查配置,成功后再重载;两层代理方案需要两台分别检查:

sudo nginx -t
sudo systemctl reload nginx

从外网检查允许与拒绝的请求​

把 实际问卷ID 替换为已发布的测试问卷。使用不在办公网内的设备或网络进行检查:

# 填写页应返回 public 入口 HTML,正常为 200。
curl -I 'https://survey.example.com/s/实际问卷ID'

# 初始化接口应到达后端并正常返回系统基础信息。
curl 'https://survey.example.com/admin-api/system/auth/get-sys-info'

# 下列路径在外网网关直接返回 404,不应返回后台 HTML 或后端鉴权 JSON。
curl -I 'https://survey.example.com/'
curl -I 'https://survey.example.com/project/实际问卷ID/edit'
curl -I 'https://survey.example.com/adm/index.html'
curl -I 'https://survey.example.com/infra/config'
curl -i 'https://survey.example.com/admin-api/project/page'
curl -i 'https://survey.example.com/admin-api/system/user/page'
curl -i -X POST 'https://survey.example.com/admin-api/answer/page'
curl -i 'https://survey.example.com/admin-api/infra/file/page'

# survey 前缀包含末尾斜杠,不会放行名称相近的其他接口组。
curl -i 'https://survey.example.com/admin-api/survey-admin/page'

/admin-api/survey/ 下的请求会转发给后端,使用错误的方法或参数时,由后端返回相应错误。接口返回后端业务错误,只能证明请求到了后端,不能证明问卷能正常填写。还需要在 Chrome 或手机浏览器中完成一次实际流程:

  1. 在内网创建并发布测试问卷,打开“发放问卷”,确认发布链接和二维码都是外网域名。
  2. 在外网打开链接、刷新页面,确认 JavaScript、CSS、题目图片正常加载。
  3. 完成校验、填写与提交;有验证码、登录、文件上传、关联回填或结果页时分别验证。
  4. 在内网查看刚提交的答卷,确认数据完整。
  5. 从外网直接输入后台页面和管理 API 地址,确认均在网关被拒绝;内网管理操作仍正常。

常见问题​

现象排查方向
页面打开了,但一直加载或无法提交检查系统基础信息、加载问卷、验证码和提交接口是否被网关拒绝;区分 Nginx 404 与后端业务错误
发布链接仍然是内网地址确认“系统地址”保存成功,再刷新设计页面并重新打开“发放问卷”
新链接正确,旧二维码仍进入内网重新生成、下载并分发二维码,已下载图片不会自动改变
题目文字正常,图片或附件打不开检查文件 URL 是否仍指向内网,目录是否在只读白名单中,私有附件凭证是否有效
登录后返回错误地址核对外网协议、登录回调、系统地址和所需认证接口,二级目录还要核对前缀
内网站点返回 403检查 allow 网段和经过 NAT 后的网关来源 IP,不要直接改成允许所有来源
外网还能打开后台或调用管理 API检查旧站点、其他端口、CDN/网关规则与防火墙是否存在直通后端的入口

以后升级系统或新增项目功能时,/admin-api/survey/ 下的接口会自动放行,无需逐项追加;新的页面或其他接口前缀仍需按实际用途补充规则。