为 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>
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import { Router, Request, Response } from 'express'
|
||
import { v4 as uuidv4 } from 'uuid'
|
||
import db from '../db.js'
|
||
import { sessionAuth } from '../middleware/auth.js'
|
||
|
||
const router = Router()
|
||
|
||
// POST /api/auth/login
|
||
router.post('/login', async (req: Request, res: Response) => {
|
||
const { userId, accessToken, siteId } = req.body
|
||
|
||
if (!userId || !accessToken || !siteId) {
|
||
res.json({ success: false, message: '请填写完整信息' })
|
||
return
|
||
}
|
||
|
||
const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(siteId) as any
|
||
if (!site) {
|
||
res.json({ success: false, message: '站点不存在' })
|
||
return
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`${site.url}/api/user/self`, {
|
||
headers: { 'Authorization': accessToken }
|
||
})
|
||
const result = await response.json() as any
|
||
|
||
if (!result.success) {
|
||
res.json({ success: false, message: result.message || '认证失败,请检查用户ID和令牌' })
|
||
return
|
||
}
|
||
|
||
const userInfo = result.data
|
||
if (userInfo.id !== Number(userId)) {
|
||
res.json({ success: false, message: '用户ID与令牌不匹配' })
|
||
return
|
||
}
|
||
|
||
const sessionId = uuidv4()
|
||
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString()
|
||
|
||
db.prepare(
|
||
'INSERT INTO sessions (id, user_id, access_token, site_id, site_url, user_info, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
||
).run(sessionId, userId, accessToken, siteId, site.url, JSON.stringify(userInfo), expiresAt)
|
||
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
sessionToken: sessionId,
|
||
userInfo,
|
||
site: { id: site.id, name: site.name, url: site.url }
|
||
}
|
||
})
|
||
} catch (error: any) {
|
||
res.json({ success: false, message: `无法连接站点: ${error.message}` })
|
||
}
|
||
})
|
||
|
||
// POST /api/auth/logout
|
||
router.post('/logout', sessionAuth, (req: Request, res: Response) => {
|
||
db.prepare('DELETE FROM sessions WHERE id = ?').run(req.session!.id)
|
||
res.json({ success: true })
|
||
})
|
||
|
||
// GET /api/auth/me
|
||
router.get('/me', sessionAuth, (req: Request, res: Response) => {
|
||
const site = db.prepare('SELECT id, name, url FROM sites WHERE id = ?').get(req.session!.site_id)
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
userInfo: JSON.parse(req.session!.user_info),
|
||
site
|
||
}
|
||
})
|
||
})
|
||
|
||
export default router
|