最近我在学习前端 Vue 技术,找了一个前后端分离的项目资料来练手。之前一直用 Docker 管理数据库和 Redis,也了解过后台服务的 Docker 部署,但还没真正用 Docker 部署过前端项目。正好心血来潮,就想把前端也容器化。这样一来,前端、后端、数据库和 Redis 就都能用 Docker 统一管理了,岂不是可以实现“一键上线”?说干就干。

项目需要同时支持本地运行和容器运行,所以我立刻意识到需要配置多环境。既然 Spring 有多环境配置,Vue 没道理没有。查资料、问 AI,一番操作之后,我配置了这两个环境文件:

./RuoYi-Vue3/.env.development
./RuoYi-Vue3/.env.production

这里得吐槽一下环境配置的使用方式。在 package.json 里,脚本原来是这样的:

"scripts": {
  "dev": "vite --mode development",
  "build": "vite build --mode production",
  "preview": "vite preview"
}

但其实默认情况下,运行 dev 就会自动使用 .env.development,而 build 则会用 .env.production。这种“约定优于配置”看起来省事,但其实容易藏坑——如果你不知道这个约定,很可能配置了半天却发现根本没生效。写配置明明就一次的事,真没必要为了省这点事增加未来的调试成本。(每次遇到这种“约定”带来的坑,我都忍不住要吐槽几句)

若依项目本身是自带这两个环境文件的,不过内容并不完全适合容器运行。我做了一点调整,主要是增加了一个环境变量用来区分后台服务的地址,这样在 Docker 网络里就可以直接用服务名访问后端了:

# 容器环境,ruoyi-backend 是后台服务的容器名
VITE_API_BASE_URL = http://ruoyi-backend:8080

# 本地环境
VITE_API_BASE_URL = http://localhost:8080

原本的项目里已经配置了代理,我顺着这个思路,在 vite.config.js 里把后端地址改成读取环境变量的方式。查了一下怎么在 Vite 配置中读取环境变量,边试边改,总算搞明白了。

一开始我看到有资料说可以这样读取:

const VITE_API_BASE_URL = import.meta.env.VITE_API_BASE_URL;

request.js 里确实可以,但在 vite.config.js 里却行不通。后来我才理解,虽然都是 JS 文件,但 vite.config.js 是给构建工具用的,运行环境不同,不能直接使用 import.meta.env。正确的方式是使用 Vite 提供的 loadEnv

import { defineConfig, loadEnv } from 'vite'

export default defineConfig(({ command, mode }) => {
  // 根据当前环境加载对应的 .env 文件
  const env = loadEnv(mode, process.cwd(), '')
})

搞定了多环境配置,接下来就是写 Dockerfile 了。不用说,继续请 AI 帮忙:

# 第一阶段:使用 Node 镜像打包
FROM node:20-alpine3.19 AS frontend-builder
WORKDIR /build-app
COPY . .
RUN npm install
RUN npm run build:prod

# 第二阶段:使用 Nginx 镜像部署
FROM nginx:1.29.1-alpine3.22-otel
EXPOSE 80
WORKDIR /app
# 替换 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 将第一阶段的静态文件复制到 nginx 中
RUN rm -rf /usr/share/nginx/html
RUN mkdir /usr/share/nginx/html
COPY --from=frontend-builder /build-app/dist /usr/share/nginx/html

# 运行 nginx
CMD ["nginx", "-g", "daemon off;"]

这个 Dockerfile 的结构很清晰:先用 Node 镜像构建出静态文件,再把这些文件复制到 Nginx 镜像中。令我惊喜的是,这个配置没有任何硬编码的应用相关信息,非常通用!

不过还缺一个 Nginx 的配置文件,于是第二个坑来了。最初的配置是这样的:

server {
  listen        80;
  listen        [::]:80;
  server_name   localhost;

  access_log    /var/log/nginx/host.access.log main;

  # 前端静态资源
  location / {
    root        /usr/share/nginx/html;
    index       index.html index.htm;

    # 新增下面这句,其他是 nginx 默认配置
    # 解决部分前端框架的路由问题,在浏览器刷新时报 404
    try_files   $uri $uri/ /index.html;
  }

  # 新增:反向代理 API 请求
  # 所有以 /api 开头的请求,转发到后端服务
  location /prod-api/ {
    # 注意:http://ruoyi-backend:8080 是 Docker 内部服务名 + 端口
    proxy_pass  http://ruoyi-backend:8080/;

    # 以下配置确保后端能正确识别客户端信息
    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_buffering off;
  }

  error_page    500 502 503 504 /50x.html;
  location = /50x.html {
    root        /usr/share/nginx/html;
  }
}

这个配置也是 AI 生成的,但一开始并不能直接用,调试了好久。其中最关键的是这一行:

try_files $uri $uri/ /index.html;

这是因为 Vue 是单页应用,很多路由在服务器上并没有对应的文件,所以需要把所有的路由请求都重定向到 index.html。

另一个大坑是:我发现容器部署后始终连不上后台。排查了很久才明白,Vue 开发时配置的代理服务器(用来解决跨域问题)在打包后根本不会生效!所以之前配置的环境变量什么的都没用,必须在 Nginx 里配置反向代理:

proxy_pass http://ruoyi-backend:8080/;

用 AI 辅助开发经常遇到这种情况:它给出的答案往往不完整,如果你不提,它也不会主动提醒你可能遇到的问题。但一旦你给出了正确的提示,它又能提供很大帮助。

回顾一下,前端调整主要涉及这五个文件,至此基本说清楚了。部署过程中还遇到了其他问题,下一篇再继续记录。

最后说说我对 AI 辅助开发的看法:AI 确实能提升效率,也能稍微拓宽我们的技能边界——好比原本技能范围是 600 码,现在能扩展到 660 码左右。但如果你完全依赖 AI 而不懂基本原理,很容易陷入“不对,不对,还是不对……”的死循环。你无法给 AI 提供有效的调试提示,AI 能帮你的也就有限了。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐