fix: 开发代理避免 CORS,ideship Track 路径去重段
All checks were successful
Build and Deploy Front / deploy (push) Successful in 5m40s

本地开发改为同源代理到生产/测试 API;ideship 将 /Track/Track 归一为 /track/Track,避免 404。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-09-09 12:53:02 +08:00
parent c0c37a4eac
commit 2a51677908
6 changed files with 79 additions and 54 deletions

View File

@ -1,18 +1,13 @@
# ============================================================================= # =============================================================================
# 本地开发默认 → 测试环境 oms.nanafa.com / api.nanafa.com # 本地开发默认 → 测试环境(Vite 代理到 api.nanafa.com,无浏览器 CORS)
# 切换到生产 API:pnpm dev:ideship (加载 .env.ideship) # 切生产:pnpm dev:ideship
# 发版构建:pnpm build(生产) / pnpm build:test(测试)
# ============================================================================= # =============================================================================
VITE_DEPLOY_TARGET=nanafa VITE_DEPLOY_TARGET=nanafa
VITE_API_ORIGIN=http://api.nanafa.com
VITE_API_ADMIN_URL=http://api.nanafa.com/admin
VITE_OMS_ADMIN_URL=http://api.nanafa.com/admin VITE_OMS_ADMIN_URL=http://api.nanafa.com/admin
VITE_API_ASSET_ORIGIN=http://api.nanafa.com/admin VITE_API_ORIGIN=http://api.nanafa.com
# 企业微信客服
VITE_WECOM_KF_URL=https://work.weixin.qq.com/kfid/kfcae09c206f6c7045a VITE_WECOM_KF_URL=https://work.weixin.qq.com/kfid/kfcae09c206f6c7045a
# 智谱 AI(接口映射脚本生成)
VITE_ZHIPU_API_KEY=85bbbef6bafd4ca383eef04189decd07.lKVQHcLGtYE8oUn5 VITE_ZHIPU_API_KEY=85bbbef6bafd4ca383eef04189decd07.lKVQHcLGtYE8oUn5
VITE_ZHIPU_BASE_URL=/zhipu-api VITE_ZHIPU_BASE_URL=/zhipu-api
VITE_ZHIPU_MODEL=glm-5.1 VITE_ZHIPU_MODEL=glm-5.1

View File

@ -1,11 +1,9 @@
# ============================================================================= # =============================================================================
# 本地开发直连生产 API(ideship.com)— pnpm dev:ideship # 本地开发直连生产 API(ideship)— pnpm dev:ideship
# 仅用于联调生产;正式发版请用 pnpm build(.env.production) # 浏览器仍请求 localhost,由 Vite 代理到 api.ideship.com(避免 CORS)
# ============================================================================= # =============================================================================
VITE_DEPLOY_TARGET=ideship VITE_DEPLOY_TARGET=ideship
VITE_API_ORIGIN=https://api.ideship.com
VITE_API_ADMIN_URL=https://api.ideship.com/admin
VITE_OMS_ADMIN_URL=https://api.ideship.com/admin VITE_OMS_ADMIN_URL=https://api.ideship.com/admin
VITE_API_ASSET_ORIGIN=https://api.ideship.com/admin VITE_API_ORIGIN=https://api.ideship.com
VITE_WECOM_KF_URL=https://work.weixin.qq.com/kfid/kfcae09c206f6c7045a VITE_WECOM_KF_URL=https://work.weixin.qq.com/kfid/kfcae09c206f6c7045a

View File

@ -1,7 +1,5 @@
# ============================================================================= # =============================================================================
# 测试环境(oms.nanafa.com)— pnpm build:test → vite build --mode nanafa # 测试环境构建(oms.nanafa.com)— pnpm build:test
# 三套 API:http://api.nanafa.com/{admin|itfx|track}
# 前端站点:https://oms.nanafa.com
# ============================================================================= # =============================================================================
VITE_DEPLOY_TARGET=nanafa VITE_DEPLOY_TARGET=nanafa
VITE_API_ORIGIN=http://api.nanafa.com VITE_API_ORIGIN=http://api.nanafa.com

View File

@ -1,8 +1,7 @@
/** /**
* Admin API 地址(方案 A) * Admin API 地址(方案 A)
* - 优先 VITE_API_ADMIN_URL(.env.production / .env.nanafa / .env.ideship 切换) * - 开发:始终相对路径,由 Vite 代理到 VITE_OMS_ADMIN_URL(避免 localhost→api 跨域)
* - 未配置且开发:相对路径走 Vite 代理 * - 生产构建:VITE_API_ADMIN_URL / 默认 https://api.ideship.com/admin
* - 未配置且生产构建:默认 https://api.ideship.com/admin
*/ */
function normalizeBase(base: string): string { function normalizeBase(base: string): string {
@ -22,34 +21,27 @@ function configuredAdminBase(): string | undefined {
return undefined return undefined
} }
function prodAdminBase(): string {
return configuredAdminBase() ?? DEFAULT_PROD_ADMIN
}
/** 浏览器请求 Admin API(OpenAPI.BASE 或 omsUrl/orderUrl 等) */ /** 浏览器请求 Admin API(OpenAPI.BASE 或 omsUrl/orderUrl 等) */
export function resolveAdminApiPath(path: string): string { export function resolveAdminApiPath(path: string): string {
const p = ensureLeadingSlash(path) const p = ensureLeadingSlash(path)
const configured = configuredAdminBase() // 开发走同源代理,不直连远程(生产 CORS 不放行 localhost)
if (configured) { if (import.meta.env.DEV) {
return `${configured}${p}`
}
if (!import.meta.env.DEV) {
return `${DEFAULT_PROD_ADMIN}${p}`
}
return p return p
} }
return `${configuredAdminBase() ?? DEFAULT_PROD_ADMIN}${p}`
}
export function resolveOpenApiBase(): string { export function resolveOpenApiBase(): string {
const configured = configuredAdminBase() if (import.meta.env.DEV) {
if (configured) return configured
if (!import.meta.env.DEV) {
return DEFAULT_PROD_ADMIN
}
return '' return ''
} }
return configuredAdminBase() ?? DEFAULT_PROD_ADMIN
}
/** 桌面 Agent 等直连 Admin 网关 */ /** 桌面 Agent 等直连 Admin 网关(不受浏览器 CORS 限制) */
export function resolveDirectAdminApiUrl(): string { export function resolveDirectAdminApiUrl(): string {
const fromOms = import.meta.env.VITE_OMS_ADMIN_URL as string | undefined
if (fromOms?.trim()) return normalizeBase(fromOms)
const configured = configuredAdminBase() const configured = configuredAdminBase()
if (configured) return configured if (configured) return configured
if (import.meta.env.DEV) { if (import.meta.env.DEV) {
@ -60,14 +52,12 @@ export function resolveDirectAdminApiUrl(): string {
/** 静态资源(uploads)在 Admin wwwroot,须带 `/admin`。 */ /** 静态资源(uploads)在 Admin wwwroot,须带 `/admin`。 */
export function resolveAdminAssetOrigin(): string { export function resolveAdminAssetOrigin(): string {
if (import.meta.env.DEV) {
return ''
}
const override = import.meta.env.VITE_API_ASSET_ORIGIN as string | undefined const override = import.meta.env.VITE_API_ASSET_ORIGIN as string | undefined
if (override?.trim()) { if (override?.trim()) {
return normalizeBase(override) return normalizeBase(override)
} }
const configured = configuredAdminBase() return configuredAdminBase() ?? DEFAULT_PROD_ADMIN
if (configured) return configured
if (!import.meta.env.DEV) {
return DEFAULT_PROD_ADMIN
}
return ''
} }

View File

@ -20,9 +20,10 @@ const SPA_API_PREFIXES = [
const DEFAULT_PROD_API = 'https://api.ideship.com' const DEFAULT_PROD_API = 'https://api.ideship.com'
function prodApiOrigin(): string | null { function prodApiOrigin(): string | null {
// 开发走 Vite 代理,不改写为跨域绝对地址
if (import.meta.env.DEV) return null
const override = (import.meta.env.VITE_API_ORIGIN as string | undefined)?.trim() const override = (import.meta.env.VITE_API_ORIGIN as string | undefined)?.trim()
if (override) return override.replace(/\/$/, '') if (override) return override.replace(/\/$/, '')
if (import.meta.env.DEV) return null
return DEFAULT_PROD_API return DEFAULT_PROD_API
} }
@ -76,11 +77,28 @@ export function rewriteBrowserApiPath(url: string): string {
} }
if (path.startsWith('/Track')) { if (path.startsWith('/Track')) {
// ideship:/Track/Track/Order → /track/Track/Order
// nanafa:保持 /track + /Track/Track/Order
const useSingle =
String(import.meta.env.VITE_DEPLOY_TARGET || '').toLowerCase() === 'ideship' ||
/ideship\.com/i.test(origin)
if (useSingle && path.startsWith('/Track/Track/')) {
return `${origin}/track/Track/${path.slice('/Track/Track/'.length)}`
}
return `${origin}/track${path}` return `${origin}/track${path}`
} }
if (path.startsWith('/trackApi/')) { if (path.startsWith('/trackApi/')) {
return `${origin}/track/${path.slice('/trackApi/'.length)}` const rest = path.slice('/trackApi/'.length)
const useSingle =
String(import.meta.env.VITE_DEPLOY_TARGET || '').toLowerCase() === 'ideship' ||
/ideship\.com/i.test(origin)
// trackApi/Track/Order → /track/Track/Order
if (useSingle && rest.startsWith('Track/')) {
return `${origin}/track/${rest}`
}
// nanafa 历史:trackApi/Track/Order → /track/Track/Track/Order(与双 Track 客户端一致时由调用方决定)
return `${origin}/track/${rest}`
} }
return path return path

View File

@ -14,10 +14,33 @@ import configCompressPlugin from './config/plugin/compress';
export default defineConfig(({ mode }) => { export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), ''); const env = loadEnv(mode, process.cwd(), '');
// Admin API 目标:由 VITE_OMS_ADMIN_URL 决定(开发默认线上) // Admin API 目标:由 VITE_OMS_ADMIN_URL 决定(开发走代理,避免浏览器 CORS)
const omsAdminTarget = ( const omsAdminTarget = (
env.VITE_OMS_ADMIN_URL || 'http://api.nanafa.com/admin' env.VITE_OMS_ADMIN_URL || env.VITE_API_ADMIN_URL || 'http://api.nanafa.com/admin'
).replace(/\/$/, ''); ).replace(/\/$/, '');
// API 根(itfx/track):优先 VITE_API_ORIGIN,否则从 Admin URL 去掉 /admin
const apiOrigin = (
env.VITE_API_ORIGIN ||
omsAdminTarget.replace(/\/admin\/?$/, '') ||
'http://api.nanafa.com'
).replace(/\/$/, '');
// ideship:PATHBASE=/track → /track/Track/Order/...
// nanafa:网关仍吃 /track/Track/Track/Order/...(前端历史双 Track)
const trackUseSingleSegment =
String(env.VITE_DEPLOY_TARGET || '').toLowerCase() === 'ideship' ||
/ideship\.com/i.test(apiOrigin);
const rewriteTrackProxyPath = (path: string) => {
if (trackUseSingleSegment) {
if (path.startsWith('/Track/Track/')) {
return `/track/Track/${path.slice('/Track/Track/'.length)}`;
}
if (path.startsWith('/Track/') || path === '/Track') {
return `/track${path}`;
}
}
return `/track${path}`;
};
return { return {
server: { server: {
@ -34,16 +57,17 @@ export default defineConfig(({ mode }) => {
changeOrigin: true, changeOrigin: true,
secure: false, secure: false,
}, },
// 上传静态资源:/api/upload/xxx -> http://api.nanafa.com/upload/xxx // 上传静态资源:/api/upload/xxx -> {apiOrigin}/upload/xxx(Admin 多为 /admin/uploads)
// 必须放在通用 /api 规则前,避免被转发到 /itfx/upload 导致 404 // 必须放在通用 /api 规则前,避免被转发到 /itfx/upload 导致 404
'/api/upload': { '/api/upload': {
target: 'http://api.nanafa.com', target: apiOrigin,
changeOrigin: true, changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
timeout: 30000, timeout: 30000,
proxyTimeout: 30000, proxyTimeout: 30000,
}, },
// Admin API:目标由 VITE_OMS_ADMIN_URL 决定(默认 http://api.nanafa.com/admin) // Admin API:目标由 VITE_OMS_ADMIN_URL 决定
'/rbac': { '/rbac': {
target: omsAdminTarget, target: omsAdminTarget,
changeOrigin: true, changeOrigin: true,
@ -281,22 +305,24 @@ export default defineConfig(({ mode }) => {
secure: false, secure: false,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
}, },
// 轨迹(track)43.139.210.35:8889 -> http://api.nanafa.com/track,路径与 rbac、api 一致:/Track/xxx // 轨迹(track):{apiOrigin}/track/...
'/Track': { '/Track': {
target: 'http://api.nanafa.com', target: apiOrigin,
changeOrigin: true, changeOrigin: true,
rewrite: (path) => '/track' + path, secure: false,
rewrite: rewriteTrackProxyPath,
configure: (proxy) => { configure: (proxy) => {
proxy.on('error', (err, req, res) => { proxy.on('error', (err, req, res) => {
console.error('proxy error', err); console.error('proxy error', err);
}); });
}, },
}, },
// API(itfx)43.139.210.35:8888 -> http://api.nanafa.com/itfx // API(itfx):{apiOrigin}/itfx
// 使用正则表达式匹配,排除前端路由路径 // 使用正则表达式匹配,排除前端路由路径
'^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|trackApi)': { '^/api/(?!upstreamInterface|upstreamInterfaceConfig|upstreamInterfaceConfigEdit|downstreamInterface|downstreamInterface/newPort|appList|appList/createApplication|requestLog|callStatistics|apiDebugger|trackApi)': {
target: 'http://api.nanafa.com/itfx', target: `${apiOrigin}/itfx`,
changeOrigin: true, changeOrigin: true,
secure: false,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
timeout: 30000, timeout: 30000,
proxyTimeout: 30000, proxyTimeout: 30000,