为 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>
50 lines
1.4 KiB
TypeScript
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
|