diff --git a/src/index.ts b/src/index.ts index 4f97045..c6e106e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,6 +13,7 @@ import schemeAobRoutes from './routes/schemesAob'; import filterRoutes from './routes/filters'; import vipRoutes from './routes/vip'; import favoriteRoutes from './routes/favorites'; +import { prisma } from './utils/prisma'; const app = express(); const PORT = process.env.PORT || 3001; @@ -49,22 +50,31 @@ app.use(cors({ app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); -// 速率限制 +// 速率限制 — 全局 const limiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 分钟 - max: 100, // 每个 IP 最多 100 次请求 + windowMs: 15 * 60 * 1000, + max: 1000, message: { success: false, message: '请求过于频繁,请稍后再试' }, }); app.use('/api/', limiter); -// 登录接口更严格的限制 -const loginLimiter = rateLimit({ +// 敏感端点更严格的限制 +const strictLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 10, - message: { success: false, message: '登录尝试过于频繁,请稍后再试' }, + message: { success: false, message: '操作过于频繁,请稍后再试' }, }); -app.use('/api/login', loginLimiter); -app.use('/api/register', loginLimiter); +app.use('/api/login', strictLimiter); +app.use('/api/register', strictLimiter); + +// 会话轮询限流 (防止高频 session-status 刷库) +const pollLimiter = rateLimit({ + windowMs: 60 * 1000, + max: 60, + message: { success: false, message: '轮询过于频繁' }, +}); +app.use('/api/session-status', pollLimiter); +app.use('/api/vip-status', pollLimiter); // ============================================ // 路由 @@ -94,8 +104,6 @@ app.use('/api', vipRoutes); // 收藏计数(放在 favorites 路由之前,避免 auth 中间件拦截) app.get('/api/favorites/count', async (req: Request, res: Response) => { try { - const { PrismaClient } = require('@prisma/client'); - const prisma = new PrismaClient(); const count = await prisma.favorite.count(); res.json({ success: true, count }); } catch (e) { @@ -209,7 +217,9 @@ app.post('/api/adverts/:id/click', (req, res) => { // 头像列表 app.get('/api/avatars', (req, res) => { res.json({ success: true, data: [] }); +}); +// ============================================ // 更新服务 (electron-updater) // ============================================ @@ -234,6 +244,7 @@ releaseDate: '2024-01-01T00:00:00.000Z' // ============================================ // 开发工具端点 (需 ADMIN_SECRET) +// ============================================ app.post('/api/admin/set-vip', async (req, res) => { const secret = process.env.ADMIN_SECRET; if (!secret || req.headers['x-admin-secret'] !== secret) { @@ -242,8 +253,6 @@ app.post('/api/admin/set-vip', async (req, res) => { try { const { username, isVip } = req.body; if (!username) return res.status(400).json({ success: false, message: '缺少 username' }); - const { PrismaClient } = require('@prisma/client'); - const prisma = new PrismaClient(); const user = await prisma.user.update({ where: { username }, data: { @@ -252,36 +261,12 @@ app.post('/api/admin/set-vip', async (req, res) => { vipLevel: isVip !== false ? 1 : 0, }, }); - await prisma.$disconnect(); res.json({ success: true, username: user.username, isVip: user.isVip }); } catch (e: any) { - res.status(500).json({ success: false, message: e.message }); + console.error('Admin set-vip error:', e); + res.status(500).json({ success: false, message: '操作失败' }); } }); -}); - -// ============================================ -// 更新服务 (electron-updater) -// ============================================ - -// 更新配置 -app.get('/update-config.json', (req, res) => { - res.json({ - version: '7.0.4', - url: '', - notes: '', - mandatory: false, - }); -}); - -// latest.yml (electron-updater 标准格式) -app.get('/latest.yml', (req, res) => { - res.type('text/yaml'); - res.send(`version: 7.0.4 -files: [] -releaseDate: '2024-01-01T00:00:00.000Z' -`); -}); // ============================================ // 错误处理 diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index c9f3c16..196be63 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,8 +1,6 @@ import { Request, Response, NextFunction } from 'express'; import { verifyToken, extractToken } from '../utils/jwt'; -import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); +import { prisma } from '../utils/prisma'; // 扩展 Request 类型 declare global { diff --git a/src/routes/auth.ts b/src/routes/auth.ts index 5e61b14..4d2e7be 100644 --- a/src/routes/auth.ts +++ b/src/routes/auth.ts @@ -1,12 +1,11 @@ import { Router, Request, Response } from 'express'; import bcrypt from 'bcryptjs'; -import { PrismaClient } from '@prisma/client'; import { z } from 'zod'; import { generateToken } from '../utils/jwt'; import { authMiddleware } from '../middleware/auth'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); // ============================================ // 注册 diff --git a/src/routes/favorites.ts b/src/routes/favorites.ts index 729c784..1319121 100644 --- a/src/routes/favorites.ts +++ b/src/routes/favorites.ts @@ -1,10 +1,9 @@ import { Router, Request, Response } from 'express'; -import { PrismaClient } from '@prisma/client'; import { z } from 'zod'; import { authMiddleware } from '../middleware/auth'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); router.use(authMiddleware); diff --git a/src/routes/filters.ts b/src/routes/filters.ts index 583262a..4ba36eb 100644 --- a/src/routes/filters.ts +++ b/src/routes/filters.ts @@ -1,9 +1,8 @@ import { Router, Request, Response } from 'express'; -import { PrismaClient } from '@prisma/client'; import { authMiddleware, optionalAuth } from '../middleware/auth'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); // ============================================ // 获取滤镜列表 diff --git a/src/routes/schemes.ts b/src/routes/schemes.ts index 4243b3e..eb51afc 100644 --- a/src/routes/schemes.ts +++ b/src/routes/schemes.ts @@ -1,11 +1,10 @@ import { Router, Request, Response } from 'express'; -import { PrismaClient } from '@prisma/client'; import { z } from 'zod'; import { authMiddleware, optionalAuth } from '../middleware/auth'; import { encrypt } from '../utils/encryption'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); // ============================================ // 获取方案列表 diff --git a/src/routes/schemesAob.ts b/src/routes/schemesAob.ts index 5ca7618..fd2955a 100644 --- a/src/routes/schemesAob.ts +++ b/src/routes/schemesAob.ts @@ -1,10 +1,9 @@ import { Router, Request, Response } from 'express'; -import { PrismaClient } from '@prisma/client'; import { authMiddleware, optionalAuth } from '../middleware/auth'; import { encrypt } from '../utils/encryption'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); // ============================================ // 获取全面战场方案列表 diff --git a/src/routes/user.ts b/src/routes/user.ts index 1f43c15..a7ccc92 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,10 +1,9 @@ import { Router, Request, Response } from 'express'; -import { PrismaClient } from '@prisma/client'; import { z } from 'zod'; import { authMiddleware } from '../middleware/auth'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); // 所有路由都需要认证 router.use(authMiddleware); diff --git a/src/routes/vip.ts b/src/routes/vip.ts index 491c412..182b3bb 100644 --- a/src/routes/vip.ts +++ b/src/routes/vip.ts @@ -1,12 +1,11 @@ import { Router, Request, Response } from 'express'; -import { PrismaClient } from '@prisma/client'; import crypto from 'crypto'; import { z } from 'zod'; import { authMiddleware } from '../middleware/auth'; import { generateToken } from '../utils/jwt'; +import { prisma } from '../utils/prisma'; const router = Router(); -const prisma = new PrismaClient(); function hashCardKey(raw: string): string { return crypto.createHash('sha256').update(raw).digest('hex'); diff --git a/src/utils/encryption.ts b/src/utils/encryption.ts index 0ce4f13..8b7c269 100644 --- a/src/utils/encryption.ts +++ b/src/utils/encryption.ts @@ -1,7 +1,15 @@ import crypto from 'crypto'; const ALGORITHM = 'aes-256-cbc'; -const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || '0123456789abcdef0123456789abcdef'; +const KEY = (() => { + const raw = process.env.ENCRYPTION_KEY || '0123456789abcdef0123456789abcdef'; + const buf = Buffer.from(raw, 'utf-8'); + if (buf.length !== 32) { + console.error(`ENCRYPTION_KEY must be exactly 32 bytes, got ${buf.length}`); + process.exit(1); + } + return buf; +})(); export interface EncryptedData { encrypted: boolean; @@ -14,9 +22,7 @@ export interface EncryptedData { */ export function encrypt(text: string): EncryptedData { const iv = crypto.randomBytes(16); - const key = Buffer.from(ENCRYPTION_KEY, 'utf-8'); - - const cipher = crypto.createCipheriv(ALGORITHM, key, iv); + const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv); let encrypted = cipher.update(text, 'utf-8', 'hex'); encrypted += cipher.final('hex'); @@ -33,9 +39,7 @@ export function encrypt(text: string): EncryptedData { export function decrypt(ivHex: string, dataHex: string): string { const iv = Buffer.from(ivHex, 'hex'); const encryptedData = Buffer.from(dataHex, 'hex'); - const key = Buffer.from(ENCRYPTION_KEY, 'utf-8'); - - const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); + const decipher = crypto.createDecipheriv(ALGORITHM, KEY, iv); let decrypted = decipher.update(encryptedData, undefined, 'utf-8'); decrypted += decipher.final('utf-8'); diff --git a/src/utils/prisma.ts b/src/utils/prisma.ts new file mode 100644 index 0000000..5aa98b4 --- /dev/null +++ b/src/utils/prisma.ts @@ -0,0 +1,7 @@ +import { PrismaClient } from '@prisma/client'; + +const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; + +export const prisma = globalForPrisma.prisma || new PrismaClient(); + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;