背景:前端常用 .env.production 在構建時寫死 API 地址
場景:運維部署時經常不知道目標主機 IP/域名
問題:每次 IP 變動都要重新編譯 → 增加運維成本
引出需求:只修改 IP 就能完成部署,不需要重新打包
目錄
- 一、解決方案思路
- 二、實現步驟
- 三、打包部署
- 四、知識補充
一、解決方案思路
1、前端層面
- 把動態配置從 .env.production 抽離,放到 public/config.json
- 在應用啟動時 fetch 該文件 → 寫入 window.__APP _ CONFIG __
- 業務代碼改用 window.__APP _ CONFIG __ 或封裝好的 axios 實例獲取 baseURL
2、后端層面(Spring Boot)
- Spring Security 默認攔截所有請求,導致 /config.json 也被保護 → 返回 401
- 在 security 配置中將 /config.json 加入 permitAll 白名單,確保前端靜態資源匿名可訪問
二、實現步驟
- 新建 public/config.json(存放運行時配置)
{"ENV": "production","VUE_APP_BASE_API": "","VUE_APP_GPU_BASE_URL": "http://10.143.135.91:9001","VUE_APP_AUTO_LOGIN_USER": "admin","VUE_APP_AUTO_LOGIN_PWD": "21232f297a57a5a743894a0e4a801fc3"
}
- 修改 main.js → 應用啟動前加載 config.json
在 src/bootstrapConfig.ts 中封裝 loadRuntimeConfig
? Vue 3(Vite 或 Vue CLI)
src/bootstrapConfig.ts(JS 項目就用 .js)
export type AppConfig = {ENV: stringVUE_APP_BASE_API: stringVUE_APP_GPU_BASE_URL: stringVUE_APP_AUTO_LOGIN_USER: stringVUE_APP_AUTO_LOGIN_PWD: string
}export async function loadRuntimeConfig(): Promise<AppConfig> {const res = await fetch('./config.json', { cache: 'no-store' })if (!res.ok) throw new Error(`load config.json failed: ${res.status}`)const cfg = (await res.json()) as AppConfig;(window as any).__APP_CONFIG__ = cfgreturn cfg
}
export function getConfig(): AppConfig {return (window as any).__APP_CONFIG__ as AppConfig
}
src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { loadRuntimeConfig } from './bootstrapConfig'async function bootstrap() {await loadRuntimeConfig()createApp(App).mount('#app')
}
bootstrap()
? Vue 2(Vue CLI)
src/bootstrapConfig.js
export async function loadRuntimeConfig() {const res = await fetch('./config.json', { cache: 'no-store' })if (!res.ok) throw new Error('load config.json failed: ' + res.status)window.__APP_CONFIG__ = await res.json()
}
export function getConfig() { return window.__APP_CONFIG__ }
src/main.js
import Vue from 'vue'
import App from './App.vue'
import { loadRuntimeConfig } from './bootstrapConfig'async function bootstrap() {await loadRuntimeConfig()new Vue({ render: h => h(App) }).$mount('#app')
}
bootstrap()
- axios 封裝:統一讀取 ‘__APP_CONFIG __’ 作為 baseURL
src/api/http.ts
import axios from 'axios'const api = axios.create({baseURL: (window as any).__APP_CONFIG__.VUE_APP_BASE_API
})export default api
業務里:
import api from '@/api/http'
// api.get('/xxx')
另一種方式
使用${window.__APP_CONFIG __.VUE_APP_GPU_BASE_URL}
- Spring Security 配置修改:放行 /config.json
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {........@Overrideprotected void configure(HttpSecurity http) throws Exception {List<String> defaultExcludes = new ArrayList<>();defaultExcludes.add("/");defaultExcludes.add("/#/**");defaultExcludes.add("/static/**");defaultExcludes.add("/swagger-ui.html");defaultExcludes.add("/swagger-ui/**");defaultExcludes.add("/swagger-resources/**");defaultExcludes.add("/doc.html");defaultExcludes.add("/doc.html#/**");defaultExcludes.add("/v3/api-docs/**");defaultExcludes.add("/index.html");defaultExcludes.add("/webjars/**");defaultExcludes.add("/js/**");defaultExcludes.add("/api/device/query/snap/**");defaultExcludes.add("/record_proxy/*/**");defaultExcludes.add("/api/emit");defaultExcludes.add("/favicon.ico");defaultExcludes.add("/api/user/login");defaultExcludes.add("/index/hook/**");defaultExcludes.add("/api/device/query/snap/**");defaultExcludes.add("/index/hook/abl/**");// 👇 新增這一行,允許匿名訪問 config.jsondefaultExcludes.add("/config.json");
部署到Spring Boot → 驗證只改 config.json 就能適配 IP
注意:改造后的文件結構
- .env.production (最小化)
# just a flag
NODE_ENV=production
VUE_APP_BUILD_VERSION=1.0.0
?? 注意:
VUE_APP_BASE_API、GPU_BASE_URL、賬號密碼這些 刪掉,因為它們要 runtime 配置,不要在這里寫。
可以額外放一些編譯時常量(例如版本號、構建時間)。
三、打包部署
方案一
在yml文件下添加配置規則,能夠讀取容器內的文件
spring:web:resources:static-locations: file:/app/static/,classpath:/static/
方案二
在Dockerfile配置文件
ENTRYPOINT ["java", \"-Dspring.web.resources.static-locations=file:/app/static/,classpath:/static/", \"-Dspring.resources.static-locations=file:/app/static/,classpath:/static/", \"-jar","wvp.jar", \"--spring.config.location=/app/config/"]
👉 -Dspring.resources.static-locations 是 Spring Boot 2.3 及之前的老配置項,
👉 -Dspring.web.resources.static-locations 是 Spring Boot 2.4 及之后的新配置項,作用完全相同,只是命名空間不同。
意思是:
- 覆蓋默認規則(自己指定順序)。
- 讓 Spring Boot 在兩個地方找靜態資源:
file:/app/static/ → 容器里的目錄(來自宿主機掛載的 /app/static)。
classpath:/static/ → 打包在 jar 里的 resources/static/。
👉 關鍵點是:順序有優先級,Spring Boot 會先從 file:/app/static/ 找,如果找不到,再去 classpath:/static/。
掛載文件(先copy一份到宿主機 然后掛載到容器內部/app/static/)
然后就會首先讀取容器里面的IP
修改之后重啟容器
docker compose up -d --force-recreate
四、知識補充
Spring Boot 靜態資源路徑設置
如果你不寫任何配置,Spring Boot 會自動從以下目錄里找靜態文件(按順序):
- classpath:/META-INF/resources/
- classpath:/resources/
- classpath:/static/ ? 最常見(即你項目里的 src/main/resources/static)
- classpath:/public/
舉個例子:
你把 src/main/resources/static/config.json 打包進 jar → 瀏覽器訪問 http://localhost:8080/config.json 就能拿到。
這是因為 classpath:/static/ 在默認搜索路徑里。