102 lines
4.3 KiB
JavaScript
102 lines
4.3 KiB
JavaScript
import { fileURLToPath, URL } from 'node:url'
|
||
import { defineConfig, loadEnv } from 'vite'
|
||
import vue from '@vitejs/plugin-vue'
|
||
|
||
export default defineConfig(({ mode }) => {
|
||
const env = loadEnv(mode, process.cwd(), '')
|
||
|
||
// 临时CORS规避方案(仅用于本地 npm run dev 调试真实后端,不用于生产构建),与根目录管理后台
|
||
// vite.config.js 的方案完全一致:在 .env.development.local(已被 .gitignore 的 *.local 规则
|
||
// 忽略,不会提交)中配置 DEV_API_PROXY_TARGET=真实后端地址,dev server 会把浏览器发往同源
|
||
// /api 的请求在 Node 端转发给真实后端;浏览器全程只与同源的 localhost:5174 通信,天然不触发
|
||
// 浏览器 CORS 检查。同时需要把该 .env.development.local 中的 VITE_API_BASE_URL 设为空字符串,
|
||
// 让 axios 走相对路径(同源),否则请求不会经过下面的代理规则。未配置 DEV_API_PROXY_TARGET 时
|
||
// proxy 为 undefined,行为与之前完全一致(直接请求 VITE_API_BASE_URL 指向的地址,默认走本地
|
||
// mock server)。
|
||
const proxy = env.DEV_API_PROXY_TARGET
|
||
? {
|
||
'/api': {
|
||
target: env.DEV_API_PROXY_TARGET,
|
||
changeOrigin: true,
|
||
cookieDomainRewrite: 'localhost',
|
||
configure: (proxyServer, options) => {
|
||
let seq = 0
|
||
const startTimeMap = new Map()
|
||
const LOG_MAX_LEN = 2000
|
||
|
||
// 内容过长(如人脸识别的base64图片)时截断,避免刷屏终端
|
||
const truncate = (str) => {
|
||
if (str.length <= LOG_MAX_LEN) return str
|
||
return `${str.slice(0, LOG_MAX_LEN)}...(已截断,原文共${str.length}字符)`
|
||
}
|
||
|
||
// 请求/响应体优先按JSON美化打印,非JSON内容按原文打印
|
||
const formatBody = (buf) => {
|
||
const text = buf.toString('utf-8')
|
||
if (!text) return ''
|
||
try {
|
||
return truncate(JSON.stringify(JSON.parse(text)))
|
||
} catch {
|
||
return truncate(text)
|
||
}
|
||
}
|
||
|
||
proxyServer.on('proxyReq', (proxyReq, req) => {
|
||
const id = ++seq
|
||
startTimeMap.set(req, { id, startAt: Date.now() })
|
||
|
||
// 强制真实后端返回未压缩内容,否则响应体是gzip二进制,日志打印会是乱码
|
||
proxyReq.setHeader('Accept-Encoding', 'identity')
|
||
|
||
const targetUrl = new URL(options.target)
|
||
const fullUrl = `${targetUrl.protocol}//${targetUrl.host}${proxyReq.path}`
|
||
|
||
// 在 http-proxy 内部把 req 自动 pipe 到 proxyReq 之前先挂上监听,
|
||
// 这样既能拿到完整请求体,又不影响正常转发流程
|
||
const chunks = []
|
||
req.on('data', (chunk) => chunks.push(chunk))
|
||
req.on('end', () => {
|
||
const body = formatBody(Buffer.concat(chunks))
|
||
console.log(`\n[REAL-API #${id}] → ${req.method} ${fullUrl}`)
|
||
if (body) console.log(` 请求体: ${body}`)
|
||
})
|
||
})
|
||
|
||
proxyServer.on('proxyRes', (proxyRes, req) => {
|
||
const meta = startTimeMap.get(req) || {}
|
||
const chunks = []
|
||
proxyRes.on('data', (chunk) => chunks.push(chunk))
|
||
proxyRes.on('end', () => {
|
||
const body = formatBody(Buffer.concat(chunks))
|
||
const cost = meta.startAt ? `${Date.now() - meta.startAt}ms` : ''
|
||
console.log(`[REAL-API #${meta.id ?? '?'}] ← ${proxyRes.statusCode} (${cost})`)
|
||
if (body) console.log(` 响应体: ${body}`)
|
||
startTimeMap.delete(req)
|
||
})
|
||
})
|
||
|
||
proxyServer.on('error', (err, req) => {
|
||
const meta = startTimeMap.get(req) || {}
|
||
console.log(`[REAL-API #${meta.id ?? '?'}] × 转发异常 ${req?.method} ${req?.url}: ${err.message}`)
|
||
startTimeMap.delete(req)
|
||
})
|
||
}
|
||
}
|
||
}
|
||
: undefined
|
||
|
||
return {
|
||
plugins: [vue()],
|
||
resolve: {
|
||
alias: {
|
||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||
}
|
||
},
|
||
server: {
|
||
// 与主项目管理后台(端口 5173)区分,避免本机同时开发两个工程时端口冲突
|
||
port: 5174,
|
||
proxy
|
||
}
|
||
}
|
||
})
|