客户只想看个页面,我却做了一个静态演示发布系统

最近,我的职业状态有点像浏览器开了很多标签页。

以前是前端开发,后来开始接触后端,慢慢做成了全栈。再后来带过团队,开始关心研发流程、项目协作和交付质量。最近又因为业务需要,开始研究产品设计、用户流程,以及一个很具体的问题:

甲方想看网页效果时,怎样给他一个真正好用的链接?

事情听起来很简单。把项目压缩一下,找个平台上传,再把链接发给客户就行。

我确实认真考虑过第三方静态托管平台。它们通常上手很快,但用在临时客户演示上,还是有一些现实问题:项目需要在多个平台之间来回切换,配置和上传流程不够统一;有些页面会出现平台广告或品牌标识;项目文件和访问权限交给外部平台后,安全边界也需要额外评估。免费额度、访问限制、自定义域名等规则,也可能在关键时候给你补上一道小考题。

如果只是偶尔演示一次,第三方平台当然够用。可当演示项目开始变多,我更希望它们能在自己的后台里集中管理:上传、发布、下线、过期和删除,都有明确的状态,链接也不用到聊天记录里考古。

所以我决定自己动手做一个轻量的静态演示发布系统。

我真正想解决的,不只是上传文件

系统最终形成了一条比较清晰的流程:

text 复制代码
创建项目
  ↓
上传 ZIP
  ↓
临时目录解压
  ↓
安全校验
  ↓
生成版本目录
  ↓
确认发布
  ↓
生成演示链接

管理员在后台创建一个项目,上传静态网站压缩包,系统会生成一个随机 Token,例如:

text 复制代码
https://demo.example.com/p/8f7c2a1e9d4b6c30

客户打开链接即可查看页面,不需要安装 Node.js,也不需要关心项目是 Vue、React 还是手写 HTML。

系统的边界也很明确:只发布静态文件,不执行任何服务端代码。 HTML、CSS、JavaScript、图片可以,PHP、Java、Python 后端程序不可以。它是演示系统,不准备偷偷转型成云服务器。

前端:上传和发布分成两步

前端使用 Vue 3。上传功能本身并不复杂,但我把"上传版本"和"正式发布"拆成了两个动作。

js 复制代码
const formData = new FormData()
formData.append('file', selectedFile)

const version = await request.post(
  '/demo/' + projectId + '/upload',
  formData,
  {
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    timeout: 0
  }
)

// 上传成功后,再由管理员确认发布
await request.post(
  '/demo/' + projectId + '/v/' + version.id + '/pub'
)

为什么不上传完自动发布?

因为客户演示现场最怕的不是报错,而是你以为发布成功了,客户看到的却是半成品。

上传只生成待发布版本,管理员确认无误后才切换线上版本。这样既减少误操作,也为版本对比、回滚和发布说明留下了空间。

后端:ZIP 文件不能想怎么解压就怎么解压

静态发布系统最需要认真对待的地方,是 ZIP 文件安全。

压缩包里可能出现这样的路径:

text 复制代码
../../application.properties

如果直接拼接路径解压,文件就可能写到演示目录之外。教学版代码可以这样处理:

java 复制代码
Path target = tempDir
        .resolve(entry.getName())
        .normalize();

if (!target.startsWith(tempDir)) {
    throw new PublishException("非法文件路径");
}

除了路径穿越,还需要限制 ZIP 大小、解压后的总大小、文件数量和允许的扩展名,同时确认根目录或一层子目录里存在 index.html。

java 复制代码
private static final Set<String> ALLOWED_EXTENSIONS = Set.of(
        "html", "css", "js", "json",
        "png", "jpg", "jpeg", "gif", "svg", "webp", "woff2"
);

上传文件先进入临时目录。校验成功后,再移动到正式版本目录。

text 复制代码
/data/demo/
├── projects/
│   └── project-001/
│       └── versions/
│           ├── v1/
│           └── v2/
└── temp/

项目源码目录和运行数据目录也必须分开。客户上传的 ZIP、解压文件和版本目录,不应该放进前端源码目录或 Spring Boot 的静态资源目录,否则重新部署时很容易误删业务数据。

发布版本,本质上是一次"切换指针"

系统不会直接覆盖当前线上文件。上传新版本后,先生成独立目录:

text 复制代码
versions/v1/
versions/v2/
versions/v3/

数据库记录当前发布的是哪一个版本。发布时只更新当前版本关系:

java 复制代码
public void publish(Long projectId, Long versionId) {
    DemoProjectVersion version = versionMapper.findById(versionId);

    if (version == null || !projectId.equals(version.getProjectId())) {
        throw new PublishException("版本不存在");
    }

    versionMapper.markPublished(versionId);
    projectMapper.switchCurrentVersion(projectId, versionId);
}

这样一来,新版本上传失败时,旧版本仍然可以正常访问。客户看到的是稳定链接,开发者也不用在演示前祈祷服务器状态良好。

Nginx:负责入口、HTTPS 和请求转发

Nginx 在这里主要负责域名、HTTPS 和请求转发。下面是简化后的示例配置:

nginx 复制代码
server {
    listen 443 ssl http2;
    server_name demo.example.com;

    ssl_certificate     /etc/nginx/cert/fullchain.pem;
    ssl_certificate_key /etc/nginx/cert/privkey.pem;

    location /demo/ {
        proxy_pass http://127.0.0.1:9000;
        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_read_timeout 60s;
    }
}

当前版本先由后端根据 Token 找到对应版本目录,再返回静态文件。后续如果访问量上升,可以让后端只负责鉴权和路径解析,再配合 Nginx 的 X-Accel-Redirect 发送文件,减少应用线程参与大文件传输。

配置修改后,先检查语法,再重载服务:

bash 复制代码
nginx -t && systemctl reload nginx

这条命令看起来朴素,却是部署时非常实用的"心理按摩"。

过期和下线,不能靠记忆力

临时演示项目通常不是永久有效。系统支持项目下线和过期处理,访问时会检查当前状态:

java 复制代码
if (!"PUBLISHED".equals(project.getStatus())) {
    return AccessResult.notFound();
}

if (project.getExpiresAt() != null
        && project.getExpiresAt().isBefore(LocalDateTime.now())) {
    projectMapper.markExpired(project.getId());
    return AccessResult.expired();
}

后台定时任务会处理过期项目,也会清理长时间未完成的临时上传目录。否则服务器磁盘迟早会变成"历史上传文件博物馆"。

做完以后,我发现自己做的是一条业务流程

表面上看,它解决的是静态文件发布。

实际上,管理员关心的是版本能不能控制,客户关心的是链接能不能打开,开发者关心的是路径安全、文件边界和部署稳定性。

这也是我从前端转向全栈、再接触业务和产品之后,越来越明显的感受:

技术不是把功能堆出来,而是把一个模糊需求变成一条能稳定运行的流程。

这套静态演示发布系统已经完成并上线。它没有复杂到需要画几十张架构图,但确实解决了工作中的一个小麻烦:客户需要看页面时,我可以直接发一个链接;项目需要更新时,也不必重新寻找平台;演示结束后,还能把项目下线或设置过期时间。

很多系统的起点,可能就是一句很普通的话:

"能不能发我个链接看看?"

如果你也有类似的演示、预览或临时发布需求,欢迎交流。后续我也会根据实际情况,把入口逐步开放出来。

相关推荐
渔夫正在掘金1 小时前
告别构建时代:为什么 AI 编程浪潮正让 Vue 与 React 走向落后?
前端
一个有理想的摸鱼选手1 小时前
(四)路书Agnet-综合天气距离交通节奏等多因素来编排旅行路线
前端·后端·gis
神奇的程序员1 小时前
在这个独属于ai的时代,我终究是被裁员了
前端·后端·面试
云边有个稻草人1 小时前
从单机工具到“云+端+服务”:KDMS数据库迁移工具如何支撑大型信创项目协同作战
后端
玉宇夕落1 小时前
React + JWT 登录鉴权系统
前端
李广坤1 小时前
AgentScope Tool 热更新技术方案:不重启服务,实时管控 Agent 工具
后端·架构
hunterandroid1 小时前
[鸿蒙从零到一] HarmonyOS 安全加固与数据保护实战:从密钥管理到反调试的全链路防护
前端
用户921080262861 小时前
从 Bubble 到 BubbleList:加载历史对话时才真正看懂组件通信
前端
hunterandroid1 小时前
Android WebView JSBridge 治理实战:从线上白屏崩溃到协议化通信
android·前端