Files
newapi-dashboard/server/routes/proxy.ts
LAMCLOD f6036cab66 feat: newapi-dashboard 全栈项目初始化
为 new-api (LLM API 网关) 构建独立前端管理面板:
- React 18 + TypeScript + Vite + Ant Design 5 前端
- Node.js + Express + better-sqlite3 BFF 后端
- 登录页: 站点选择器 + UserID + AccessToken 认证
- 仪表盘: 用户信息、额度环形图、7天趋势图、模型饼图、令牌概览、日志时间线
- 账单页: 筛选日志表格、模型消耗柱状图、充值记录、CSV/PDF 导出(HMAC签名)
- 管理员站点管理: 站点 CRUD
- API 代理: 多站点切换,会话管理

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 18:00:28 +08:00

50 lines
1.4 KiB
TypeScript

import { Router, Request, Response } from 'express'
import { sessionAuth } from '../middleware/auth.js'
const router = Router()
router.all('/*', sessionAuth, async (req: Request, res: Response) => {
const session = req.session!
const targetPath = req.originalUrl.replace(/^\/proxy/, '')
const targetUrl = `${session.site_url}${targetPath}`
try {
const headers: Record<string, string> = {
'Authorization': session.access_token,
'Content-Type': req.headers['content-type'] || 'application/json'
}
const fetchOptions: RequestInit = {
method: req.method,
headers
}
if (['POST', 'PUT', 'PATCH'].includes(req.method) && req.body) {
fetchOptions.body = JSON.stringify(req.body)
}
const response = await fetch(targetUrl, fetchOptions)
const contentType = response.headers.get('content-type') || ''
res.status(response.status)
const forwardHeaders = ['content-type', 'x-request-id']
for (const h of forwardHeaders) {
const val = response.headers.get(h)
if (val) res.setHeader(h, val)
}
if (contentType.includes('application/json')) {
const data = await response.json()
res.json(data)
} else {
const buffer = await response.arrayBuffer()
res.send(Buffer.from(buffer))
}
} catch (error: any) {
res.status(502).json({ success: false, message: `代理请求失败: ${error.message}` })
}
})
export default router