零基础联通云部署实战:三维编辑器上云全记录
前言:作为一个只写过前端代码、从未接触过服务器部署的开发者,当我第一次需要把本地的三维编辑器(支持爆炸图编辑、三维组态、设备空间分析)部署到联通云服务器上时,面对"Docker"、"容器"、"安全上下文"这些概念完全是懵的。这篇文章记录了我从零开始,把项目成功部署到云端的完整过程,以及踩过的那些让人抓狂的坑。
一、项目架构与部署全景图
在深入代码前,我们先建立整体认知。我们的三维编辑器系统包含这些部分:
关键概念:我们并不是直接把代码"放"到服务器上运行,而是使用Docker创建一个"集装箱"(容器),在里面配置好所有运行环境,然后把集装箱放到服务器上。
二、核心文件详解:每个文件都在做什么?
1. Dockerfile —— 集装箱的"装修图纸"
这个文件告诉 Docker:"我要创建一个包含 Apache、PHP 和各种配置的镜像"。逐行解释:
# 基于官方 PHP 8.2 + Apache 镜像(就像选择毛坯房户型) FROM php:8.2-apache # 安装系统依赖:zip 扩展(用于文件压缩上传) RUN apt-get update && apt-get install -y \ libzip-dev zip unzip \ && docker-php-ext-install zip \ && rm -rf /var/lib/apt/lists/* # 清理缓存,减小镜像体积 # 启用 Apache 的四个关键模块: # rewrite: URL 重写(支持前端路由) # headers: HTTP 头管理(CORS 跨域需要) # mime: 文件类型识别(确保 .js 被识别为 JS 而非文本) # deflate: Gzip 压缩(让 5MB 的 JS 文件传输更快) RUN a2enmod rewrite headers mime deflate # --- Apache 站点配置(核心!)--- # 这里使用 HERE 文档(<<'APACHECONF')批量写入配置文件 RUN cat > /etc/apache2/sites-available/000-default.conf <<'APACHECONF' <VirtualHost *:80> DocumentRoot /var/www/html # 网站根目录 DirectoryIndex cc_editor.html # 默认首页 # 大文件传输超时设置(我们的 JS 有 5MB+) Timeout 300 KeepAlive On <Directory /var/www/html> AllowOverride All # 允许 .htaccess 覆盖配置 Require all granted # 允许所有 IP 访问 # --- MIME 类型修复(坑点!)--- # 确保浏览器知道 .js 是脚本,.babylon 是 JSON AddType application/javascript .js .mjs AddType application/json .json .babylon # --- 跨域支持(CORS)--- # 允许任何域名调用我们的 API(开发阶段方便) Header set Access-Control-Allow-Origin "*" # --- Gzip 压缩(性能优化)--- <IfModule mod_deflate.c> AddOutputFilterByType DEFLATE application/javascript DeflateCompressionLevel 6 # 压缩级别 1-9,6 是平衡值 </IfModule> </Directory> </VirtualHost> APACHECONF # --- PHP 配置调整 --- # 解决上传文件大小限制(默认只有 2M,我们改成 200M 支持模型上传) RUN cat > /usr/local/etc/php/conf.d/custom.ini <<'PHPINI' upload_max_filesize = 200M post_max_size = 200M max_execution_time = 300 # 脚本最长运行时间(秒) memory_limit = 256M # PHP 可用内存 PHPINI # 复制当前目录所有文件到容器的 /var/www/html(网站根目录) WORKDIR /var/www/html COPY . /var/www/html/ # 设置文件权限(Linux 权限管理) # www-data 是 Apache 的默认用户,必须让他有读写权限 RUN chown -R www-data:www-data /var/www/html \ && find /var/www/html -type d -exec chmod 755 {} \; \ && find /var/www/html -type f -exec chmod 644 {} \; \ && chmod -R 775 /var/www/html/Resources # 资源目录需要写入权限 # 暴露 80 端口(HTTP 默认端口) EXPOSE 80 CMD ["apache2-foreground"] # 容器启动时运行 Apache2. deploy.sh —— 一键部署脚本(重点详解)
这是我最困惑又最重要的文件。逐行解释:
#!/bin/bash # 上面这行叫 Shebang,告诉系统用 /bin/bash 执行此脚本 # set -e 表示:如果任何命令执行失败(返回非0),立即停止脚本 # 防止"带病"继续执行,造成更难排查的问题 set -e # 定义变量(避免重复写,方便修改) IMAGE_NAME="cc_dist" # Docker 镜像名称(类似类名) CONTAINER_NAME="cc_dist_app" # 容器实例名称(类似对象名) HOST_PORT=30513 # 服务器对外端口(用户访问用) CONTAINER_PORT=80 # 容器内部端口(Apache 监听) # cd "$(dirname "$0")" 是"进入脚本所在目录"的安全写法 # $0 是脚本本身路径,dirname 取目录,解决"在哪里执行脚本"的路径问题 cd "$(dirname "$0")" # 定义 start 函数(封装启动逻辑) start() { echo "正在构建镜像 ${IMAGE_NAME}..." # docker build -t 标签 . 表示用当前目录 Dockerfile 构建镜像 # . 是构建上下文(COPY 命令会复制这个目录的文件) docker build -t "${IMAGE_NAME}" . # 检查是否已有同名容器在运行 # docker ps -a 列出所有容器(包括停止的) # --format '{{.Names}}' 只输出名字 # grep -q 安静模式匹配,匹配到返回0(真) if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo "已存在容器 ${CONTAINER_NAME},先删除..." # 2>/dev/null 把错误输出扔掉(比如容器不存在时的报错) # || true 确保即使删除失败,脚本也不停止(set -e 会杀掉脚本) docker rm -f "${CONTAINER_NAME}" 2>/dev/null || true fi echo "正在启动容器,映射端口 ${HOST_PORT} -> ${CONTAINER_PORT}..." # docker run -d 后台运行(daemon) # --name 给容器起名字,方便后续管理 # -p 端口映射(主机端口:容器端口) docker run -d \ --name "${CONTAINER_NAME}" \ -p "${HOST_PORT}:${CONTAINER_PORT}" \ "${IMAGE_NAME}" echo "启动完成。访问: http://<服务器IP>:${HOST_PORT}" } # 定义 stop 函数(优雅停机) stop() { if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then echo "正在停止并删除容器..." docker stop "${CONTAINER_NAME}" 2>/dev/null || true # 先停止进程 docker rm "${CONTAINER_NAME}" 2>/dev/null || true # 再删除容器 echo "已停止。" else echo "容器不存在,无需停止。" fi } # case 语句处理命令行参数($1 是第一个参数) case "${1:-}" in # ${1:-} 表示如果 $1 未定义,用空字符串 start) start # 调用上面定义的 start 函数 ;; stop) stop ;; *) # * 通配符,匹配任何其他输入 echo "用法: $0 {start|stop}" echo " start - 构建并启动" echo " stop - 停止服务" exit 1 # 返回错误码 ;; esac使用方式:
# 赋予执行权限(第一次使用需要) chmod +x deploy.sh # 启动服务 ./deploy.sh start # 停止服务 ./deploy.sh stop3. file_manager.php —— 资源管理的大脑
这是一个API 接口文件,供前端 Vue 调用,实现类似"资源管理器"的功能:
get-tree: 获取目录树(左侧文件树展示)
get-files: 获取文件列表(带过滤,比如只看 .glb 模型)
upload-file: 上传模型/贴图(支持大文件)
delete-file: 删除资源
get-text-file-content: 读取场景配置文件(.e3d/.c3d)
安全设计:
validatePath()函数防止目录遍历攻击(比如传入../../../etc/passwd试图读取系统文件)限制
BASE_DIR为/Resources目录,所有操作只能在这个"沙盒"内进行白名单校验文件后缀(只允许
jpg/png/glb/gltf等)
4. diag.html —— 部署诊断神器
部署时如果页面白屏,打开这个文件能看到:
Environment: 是否安全上下文、WebGL 支持、UUID 可用性
Critical Files: 检查核心 JS 文件是否正确加载(大小检测防止传输损坏)
Resource Directories: 测试 PHP 接口是否连通
External CDN: 检查能否访问腾讯图标库(内网环境往往不通)
三、Docker 到底是什么?为什么要用它?
通俗理解
想象你要搬家(部署应用):
传统方式:把家具(代码)搬到新家(服务器),然后现场组装家具、接水管、接电线(安装 Apache、PHP、配置环境)。每次搬家都要重新组装,而且新家的螺丝孔位可能不一样(服务器系统差异)。
Docker 方式:把整个装修好的房子(容器)连地基一起拔起来,用吊车运到新家,接通水电(端口映射)即可入住。
技术定义
Docker 使用容器化技术,将应用及其依赖(操作系统、Web 服务器、运行时)打包成一个镜像(Image)。镜像运行时成为容器(Container)。
核心优势:
环境一致性:本地开发的容器,放到联通云上一模一样,不会出现"我本地明明是好的"这种问题
隔离性:容器内的 PHP 崩溃不会影响服务器上的其他服务(如 MySQL)
轻量级:不像虚拟机需要完整操作系统,容器共享主机内核,启动秒级
部署流程图解
本地开发 → 构建镜像(docker build)→ 保存/传输 → 联通云服务器 ↓ 用户浏览器 ← 端口映射(30513)← 运行容器(docker run)← 加载镜像四、踩坑实录:那些让人崩溃的问题
坑 1:crypto.randomUUID 在云端失效
现象:
本地
http://localhost:8080运行完美部署到联通云
http://125.34.90.250:30513后,页面白屏,控制台报错:crypto.randomUUID is not a function
原因:浏览器安全上下文(Secure Context)
浏览器规定:
localhost/127.0.0.1:永远信任,所有高级 API(如 crypto、摄像头、地理位置)可用192.168.x.x(内网 IP)或公网 IP:不信任,必须使用 HTTPS 才能使用 crypto API
解决方案(Polyfill 降级):
在页面<head>最顶部插入这段代码:
<script> // 如果浏览器不支持 randomUUID(非安全上下文),手动实现一个 if (typeof crypto.randomUUID !== "function") { crypto.randomUUID = function() { // 使用 crypto.getRandomValues(这个 API 在任何环境都可用) // 生成符合 RFC4122 标准的 UUID v4 return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, function(c) { return (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16); }); }; } </script>原理:crypto.getRandomValues不受安全上下文限制,我们用它生成随机数,手动拼接成 UUID 格式,保证在低版本浏览器或非 HTTPS 环境下也能生成唯一 ID。
坑 2:TDesign 图标显示为方框或空白
现象:按钮、菜单的图标不显示,但文字正常。
原因:TDesign 默认使用图标字体(Icon Font),通过https://tdesign.gtimg.com/...CDN 加载。联通云服务器如果是内网环境或防火墙限制,无法访问腾讯 CDN,导致字体加载失败。
两种写法的区别:
写法 A(字符串方式 - 原写法):
<t-icon :name="visible ? 'chevron-left' : 'chevron-right'" />原理:组件内部根据字符串
name动态生成<i class="t-icon-chevron-left">,依赖 CSS 字体文件中的矢量图标问题:CSS 里定义了
@font-face { src: url(https://tdesign.gtimg.com/...),CDN 失效则图标失效
写法 B(组件方式 - 解决方案):
<script setup> import { ChevronLeftIcon, ChevronRightIcon } from 'tdesign-icons-vue-next'; </script> <template> <component :is="visible ? ChevronLeftIcon : ChevronRightIcon" /> </template>原理:直接引入** SVG 组件**,图标作为 Vue 组件编译进项目,不依赖外部 CDN
优势:离线可用,首屏渲染更快(不额外请求字体文件)
建议:内网/政企项目永远使用写法 B,避免外部依赖。
坑 3:localhost 能访问,IP 地址访问失败
现象:
http://localhost/cc_dist/cc_editor.html:WebGL 正常,UUID 正常,功能完整http://192.168.90.227/cc_dist/cc_editor.html:UUID 报错,某些 API 被禁用
原因:同样是Secure Context策略。浏览器将localhost视为安全开发环境,给予特权;而任何 IP 地址(包括内网 IP)被视为潜在风险环境,限制敏感 API。
完整权限对比表:
表格
| 访问方式 | Secure Context | crypto.randomUUID | WebGL | 摄像头 |
|---|---|---|---|---|
http://localhost | ✅ 是 | ✅ 可用 | ✅ | ✅ |
http://192.168.x.x | ❌ 否 | ❌ 禁用 | ✅ | ❌ |
http://公网IP | ❌ 否 | ❌ 禁用 | ✅ | ❌ |
https://任意 | ✅ 是 | ✅ 可用 | ✅ | ✅ |
启示:生产环境务必配置 HTTPS(即使使用自签名证书),否则现代 Web API 功能受限。
五、完整部署流程总结
如果你是第一次部署,按这个顺序操作:
# 1. 本地构建前端(确保生成 dist 目录) npm run build # 2. 将构建产物上传到联通云服务器(使用 scp 或 FTP) scp -r dist/* root@125.34.90.250:/data/yuzhexu/cc_dist/ # 3. SSH 登录服务器 ssh root@125.34.90.250 cd /data/yuzhexu/cc_dist # 4. 执行部署(使用我们详解过的脚本) ./deploy.sh start # 5. 验证部署 # 浏览器访问:http://125.34.90.250:30513/diag.html # 确认所有检查项为 OK # 6. 正式使用 # 访问:http://125.34.90.250:30513/cc_editor.html六、结语:给新手的建议
Docker 不是魔法:它只是把"配置环境"这个步骤提前到本地,确保"一次构建,到处运行"。理解容器 = 进程 + 文件系统隔离,就不再有神秘感。
浏览器安全策略是道坎:
localhost和IP 地址的差异、HTTP和HTTPS的差异,不是 Bug,是浏览器的安全设计。开发时可以用localhost,上线后必须规划 HTTPS。离线优先:如果你的系统在专网/内网运行(如煤矿井下系统),永远不要依赖外部 CDN(字体、图标、JS 库),全部使用本地资源或 npm 包引入。
诊断工具先行:像
diag.html这样的诊断页面是部署的"听诊器",先确保基础资源加载、MIME 类型、CORS 配置正确,再排查业务逻辑。
从零到一的部署最难的是理解全流程。希望这篇博客能成为你部署路上的"新手村攻略",祝你的三维编辑器顺利上线!
