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>
This commit is contained in:
78
server/routes/auth.ts
Normal file
78
server/routes/auth.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
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
|
||||
Reference in New Issue
Block a user