首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

一個 ${basePath} 引發的生產環境血案:URL 路徑解析與虛擬目錄配置深度復盤

     編輯:Java 綜合教學

事件回顧:詭異的「保存無反應」

現象: 客戶反饋某彈窗保存按鈕點擊無響應。本地、測試環境均無法復現。登入客戶服務器系統復現成功。

控制台報錯: TypeError: Cannot read property 'length' of undefined — 代碼試圖遍歷 response.data.length,但 response.data 不是陣列。

排查歷程:從現象到本質

1. 看 Response 內容 — 關鍵轉折

預期返回:[{...}, {...}] (JSON 陣列)。
實際返回:{"success":false, "msg":"<script>top.location.href='/login'</script>"} — 這是未登錄/無權限時的攔截器返回!

2. 為什麼已登錄用戶會觸發登錄攔截?

對比正常與異常請求 URL:

  • 正常:https://domain:port/${basePath}/api/save → 解析為 /actual-context/api/save
  • 異常:https://domain:port/projectName/api/save — 硬編碼了項目名稱!

3. 根因定位:Tomcat server.xml 虛擬目錄配置

客戶服務器 conf/server.xml 配置:

<Host name="localhost" appBase="webapps" ...>
    <Context path="" docBase="/opt/app/my-project" reloadable="true" />
</Host>
  • path="":應用掛載在**根路徑** /,訪問 URL **不帶**項目名。
  • docBase:指向項目實際部署目錄。

後果:

  1. 應用實際訪問路徑:https://domain:port/api/save (無項目名)。
  2. 前端代碼寫死 /projectName/api/save → 請求路徑變為 https://domain:port/projectName/api/save。
  3. Tomcat 根路徑下無 projectName 應用 → 404 或被默認 Servlet 處理 → 安全攔截器判定為未登錄請求 → 返回登錄頁腳本 → 前端 JS 解析報錯。

為什麼本地/測試環境沒事?

  • 本地 Tomcat 默認配置:path="/projectName" (或自動部署到 webapps/projectName),訪問**必須帶**項目名。
  • 前端硬編碼 /projectName/... 恰好匹配本地路徑 → 偽裝成「正常」。
  • 環境不一致導致代碼中隱藏的假設失效。

解決方案:統一規範,消除硬編碼

❌ 錯誤寫法

// JS 硬編碼
const url = '/my-project/api/save';

// JSP 硬編碼
<form action="/my-project/api/save">

✅ 正確寫法 (三種推薦)

  1. JSP/Thymeleaf 等模板引擎注入上下文路徑 (最通用):
    // JSP
    <% String basePath = request.getContextPath(); %>
    <form action="<%=basePath%>/api/save">
    
        // Thymeleaf
    <form th:action="@{/api/save}">
    
        // FreeMarker
    <form action="${base}/api/save"> <!-- 需配置 SharedVariable 或 Filter 注入 -->
  2. 前端框架 (Vue/React) 統一配置 baseURL:
    // axios 配置 (main.js / api.js)
    import axios from 'axios';
    // 方式 A: 打包時注入 (Webpack DefinePlugin / Vite env)
    const baseURL = import.meta.env.VITE_API_BASE || '/api'; 
    // 方式 B: 運行時從 meta 讀取 (index.html 注入)
    const baseURL = document.querySelector('meta[name="base-url"]').content;
    
    axios.defaults.baseURL = baseURL;
    // 業務代碼只寫相對路徑
    axios.post('/save', data);
  3. 純 HTML/靜態頁:<base href="${basePath}/"> + 相對路徑:
    <head>
      <base href="<%=request.getContextPath()%>/">
    </head>
    <body>
      <form action="api/save"> <!-- 自動解析為 /context/api/save -->

深度總結:避坑指南

核心教訓具體行動
絕不在前端硬編碼項目名/上下文路徑任何環境部署變更 (虛擬目錄、網關路由、域名遷移) 都會導致失效
上下文路徑由服務端模板或構建工具注入JSP 用 request.getContextPath(),現代框架用環境變量/運行時注入
環境一致性原則開發、測試、生產 Tomcat server.xml Context path 應保持一致 (建議統一非根路徑 /app-name 或統一根路徑 /,二選一全環境同步)
網關/反向代理層統一路徑轉發Nginx/Zuul/Spring Cloud Gateway 統一剝離/添加前綴,應用層無感
攔截器/過濾器返回類型一致性Ajax 請求攔截到未登錄應返回 401 JSON,而非 HTML 登錄頁腳本,前後端分離架構強制要求

血案啟示: ${basePath} 不是模板變量,而是環境無關性的契約。凡是涉及 URL 路徑拼接的地方,若無 ${basePath} (或等價機制),皆為技術債,終將在環境變更時爆雷。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved