From 9ff5d215daf399ef8176332e6171870f9d139598 Mon Sep 17 00:00:00 2001 From: chansee97 Date: Fri, 11 Jul 2025 22:15:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E5=8A=9F=E8=83=BD=E5=B9=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.dev | 2 +- .gitignore | 1 + build/autoProxy.ts | 224 ++++++++++++++++++++++++++++++++++++++ build/plugins.ts | 11 +- build/proxy.ts | 32 ------ src/service/http/index.ts | 8 +- vite.config.ts | 5 - 7 files changed, 236 insertions(+), 47 deletions(-) create mode 100644 build/autoProxy.ts delete mode 100644 build/proxy.ts diff --git a/.env.dev b/.env.dev index a6f9e31..ad69ec5 100644 --- a/.env.dev +++ b/.env.dev @@ -1,2 +1,2 @@ # 是否开启服务接口代理 Y | N -VITE_HTTP_PROXY=N +VITE_HTTP_PROXY=Y diff --git a/.gitignore b/.gitignore index 321bef0..4c5efab 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ stats.html components.d.ts auto-imports.d.ts +auto-proxy.d.ts # Lock files *-lock.yaml diff --git a/build/autoProxy.ts b/build/autoProxy.ts new file mode 100644 index 0000000..0d1672a --- /dev/null +++ b/build/autoProxy.ts @@ -0,0 +1,224 @@ +import type { ProxyOptions, UserConfig } from 'vite' +import { mkdirSync, writeFileSync } from 'node:fs' +import { dirname } from 'node:path' + +/** 服务配置接口 */ +interface ServiceConfig { + [key: string]: string +} + +/** 服务环境类型 */ +type ServiceEnvType = string + +/** 完整的服务配置类型 */ +interface FullServiceConfig { + [key: ServiceEnvType]: ServiceConfig +} + +/** 代理项接口 */ +interface ProxyItem { + /** 代理路径 */ + path: string + /** 原始地址 */ + rawPath: string +} + +/** 代理地址映射接口 */ +interface ProxyMapping { + [serviceName: string]: ProxyItem +} + +/** 插件选项接口 */ +export interface ServiceProxyPluginOptions { + /** 服务配置对象(必填) */ + serviceConfig: FullServiceConfig + /** 代理路径前缀(可选,默认为 'proxy-') */ + proxyPrefix?: string + /** 开发环境名称(可选,默认为 'development') */ + devEnvName?: ServiceEnvType + /** 是否启用代理配置 */ + enableProxy?: boolean + /** 环境变量名(可选,默认为 '__PROXY_MAPPING__') */ + envName?: string + /** d.ts 类型文件生成路径(可选,如果传入路径则在该路径生成 d.ts 类型文件) */ + dts?: string +} + +export default function createServiceProxyPlugin(options: ServiceProxyPluginOptions) { + const { + serviceConfig, + devEnvName = 'development', + proxyPrefix = 'proxy-', + enableProxy = true, + envName = '__PROXY_MAPPING__', + dts, + } = options + + return { + name: 'vite-auto-proxy', + config(config: UserConfig, { command }: { mode: string, command: 'build' | 'serve' }) { + // 只在开发环境(serve命令)时生成代理配置 + const isDev = command === 'serve' + + // 在非开发环境也注入空的代理映射,避免运行时错误 + if (!config.define) { + config.define = {} + } + + if (!enableProxy || !isDev) { + // 在非开发环境下,生成原始地址映射(path 和 rawPath 都是原始地址) + const rawMapping: ProxyMapping = {} + const envConfig = serviceConfig[devEnvName] + if (envConfig) { + Object.entries(envConfig).forEach(([serviceName, serviceUrl]) => { + rawMapping[serviceName] = { + path: serviceUrl, + rawPath: serviceUrl, + } + }) + } + config.define[envName] = JSON.stringify(rawMapping) + + // 生成 d.ts 类型文件(如果指定了路径) + if (dts) { + generateDtsFile(rawMapping, dts, envName) + } + return + } + + console.warn(`[auto-proxy] 已加载${devEnvName}模式 ${Object.keys(serviceConfig[devEnvName]).length} 个服务地址`) + + const { proxyConfig, proxyMapping } = generateProxyFromServiceConfig(serviceConfig, devEnvName, proxyPrefix) + + Object.entries(proxyMapping).forEach(([serviceName, proxyItem]) => { + console.warn(`[auto-proxy] 服务: ${serviceName} | 代理地址: ${proxyItem.path} | 实际地址: ${proxyItem.rawPath}`) + }) + + if (proxyConfig && Object.keys(proxyConfig).length > 0) { + // 确保 server 对象存在 + if (!config.server) { + config.server = {} + } + + // 合并代理配置 + config.server.proxy = { + ...config.server.proxy, + ...proxyConfig, + } + config.define[envName] = JSON.stringify(proxyMapping) + console.warn(`[auto-proxy] 代理映射已注入到 ${envName}`) + + // 生成 d.ts 类型文件(如果指定了路径) + if (dts) { + generateDtsFile(proxyMapping, dts, envName) + } + } + else { + console.warn(`[auto-proxy] 未生成任何代理配置`) + config.define[envName] = JSON.stringify({}) + + // 生成空的 d.ts 类型文件(如果指定了路径) + if (dts) { + generateDtsFile({}, dts, envName) + } + } + }, + } +} + +function generateProxyFromServiceConfig( + serviceConfig: FullServiceConfig, + mode: ServiceEnvType, + proxyPrefix: string, +): { proxyConfig: Record, proxyMapping: ProxyMapping } { + try { + // 获取当前环境的配置 + const envConfig = serviceConfig[mode] + if (!envConfig) { + console.warn(`[auto-proxy] 未找到环境 "${mode}" 的配置,使用 development 配置`) + const defaultConfig = serviceConfig.development + if (!defaultConfig) { + console.error(`[auto-proxy] 也未找到 development 配置`) + return { proxyConfig: {}, proxyMapping: {} } + } + return generateProxyFromConfig(defaultConfig, proxyPrefix) + } + + return generateProxyFromConfig(envConfig, proxyPrefix) + } + catch (error) { + console.error(`[auto-proxy] 生成代理配置失败:`, (error as Error).message) + return { proxyConfig: {}, proxyMapping: {} } + } +} + +function generateProxyFromConfig( + envConfig: ServiceConfig, + proxyPrefix: string, +): { proxyConfig: Record, proxyMapping: ProxyMapping } { + const proxyConfig: Record = {} + const proxyMapping: ProxyMapping = {} + + Object.entries(envConfig).forEach(([serviceName, serviceUrl]) => { + if (typeof serviceUrl === 'string' && serviceUrl.trim()) { + const proxyPath = `/${proxyPrefix}${serviceName}` + + const isWs = serviceUrl.startsWith('ws://') || serviceUrl.startsWith('wss://') + // 生成代理配置 + proxyConfig[proxyPath] = { + target: serviceUrl, + changeOrigin: true, + ws: isWs, + rewrite: (path: string): string => path.replace(new RegExp(`^/${proxyPrefix}${serviceName}`), ''), + } + + // 生成代理映射 + proxyMapping[serviceName] = { + path: proxyPath, + rawPath: serviceUrl, + } + } + }) + + return { proxyConfig, proxyMapping } +} + +function generateDtsFile( + mapping: ProxyMapping, + outputPath: string, + envName: string, +) { + try { + const serviceNames = Object.keys(mapping).map(name => `'${name}'`).join(' | ') + const serviceNameType = serviceNames || 'never' + + const dtsContent = `/* eslint-disable */ +/* prettier-ignore */ +// @ts-nocheck +// noinspection JSUnusedGlobalSymbols +// Generated by auto-proxy +// biome-ignore lint: disable +export {} + +type serviceName = ${serviceNameType} + +declare global { + const ${envName}: { + [K in serviceName]: { + path: string + rawPath: string + } + } +} +` + + const dir = dirname(outputPath) + if (dir) { + mkdirSync(dir, { recursive: true }) + } + writeFileSync(outputPath, dtsContent, 'utf-8') + } + catch (error) { + console.error(`[auto-proxy] 生成 d.ts 文件失败:`, (error as Error).message) + } +} diff --git a/build/plugins.ts b/build/plugins.ts index 427e0e4..c82ea60 100644 --- a/build/plugins.ts +++ b/build/plugins.ts @@ -9,9 +9,9 @@ import Icons from 'unplugin-icons/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import Components from 'unplugin-vue-components/vite' import viteCompression from 'vite-plugin-compression' - import VueDevTools from 'vite-plugin-vue-devtools' - +import AutoProxy from './autoProxy' +import { serviceConfig } from '../service.config' /** * @description: 设置vite插件配置 * @param {*} env - 环境变量配置 @@ -79,6 +79,13 @@ export function createVitePlugins(env: ImportMetaEnv) { ), }, }), + + AutoProxy({ + enableProxy: env.VITE_HTTP_PROXY === 'Y', + serviceConfig, + devEnvName: 'dev', + dts: 'src/typings/auto-proxy.d.ts', + }), ] // use compression if (env.VITE_BUILD_COMPRESS === 'Y') { diff --git a/build/proxy.ts b/build/proxy.ts deleted file mode 100644 index 5b62bc2..0000000 --- a/build/proxy.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ProxyOptions } from 'vite' -import { mapEntries } from 'radash' - -export function generateProxyPattern(envConfig: Record) { - return mapEntries(envConfig, (key, value) => { - return [ - key, - { - value, - proxy: `/proxy-${key}`, - }, - ] - }) -} - -/** - * @description: 生成vite代理字段 - * @param {*} envConfig - 环境变量配置 - */ -export function createViteProxy(envConfig: Record) { - const proxyMap = generateProxyPattern(envConfig) - return mapEntries(proxyMap, (key, value) => { - return [ - value.proxy, - { - target: value.value, - changeOrigin: true, - rewrite: (path: string) => path.replace(new RegExp(`^${value.proxy}`), ''), - }, - ] - }) as Record -} diff --git a/src/service/http/index.ts b/src/service/http/index.ts index e4f28c5..28c5fd5 100644 --- a/src/service/http/index.ts +++ b/src/service/http/index.ts @@ -1,13 +1,7 @@ -import { generateProxyPattern } from '@/../build/proxy' -import { serviceConfig } from '@/../service.config' import { createAlovaInstance } from './alova' -const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y' - -const { url } = generateProxyPattern(serviceConfig[import.meta.env.MODE]) - export const request = createAlovaInstance({ - baseURL: isHttpProxy ? url.proxy : url.value, + baseURL: __PROXY_MAPPING__.url.path, }) export const blankInstance = createAlovaInstance({ diff --git a/vite.config.ts b/vite.config.ts index 1386557..6d5224e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,14 +1,11 @@ import { resolve } from 'node:path' import { defineConfig, loadEnv } from 'vite' import { createVitePlugins } from './build/plugins' -import { createViteProxy } from './build/proxy' -import { serviceConfig } from './service.config' // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { // 根据当前工作目录中的 `mode` 加载 .env 文件 const env = loadEnv(mode, __dirname, '') as ImportMetaEnv - const envConfig = serviceConfig[mode as ServiceEnvType] return { base: env.VITE_BASE_URL, @@ -20,8 +17,6 @@ export default defineConfig(({ mode }) => { }, server: { host: '0.0.0.0', - proxy: - env.VITE_HTTP_PROXY === 'Y' ? createViteProxy(envConfig) : undefined, }, build: { target: 'esnext',