101 lines
4.1 KiB
JavaScript
101 lines
4.1 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 调试真实后端,不用于生产构建):
|
|
// 在 .env.development.local(已被 .gitignore 的 *.local 规则忽略,不会提交)中配置
|
|
// DEV_API_PROXY_TARGET=真实后端地址 后,dev server 会把浏览器发往同源 /api 的请求
|
|
// 在 Node 端转发给真实后端;浏览器全程只与同源的 localhost:5173 通信,天然不触发
|
|
// 浏览器 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
|
|
|
|
// 内容过长(如文件上传/OCR的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: {
|
|
port: 5173,
|
|
proxy
|
|
}
|
|
}
|
|
})
|