256 lines
7.0 KiB
TypeScript
256 lines
7.0 KiB
TypeScript
import express, { Request, Response } from 'express';
|
||
import cors from 'cors';
|
||
import helmet from 'helmet';
|
||
import rateLimit from 'express-rate-limit';
|
||
import dotenv from 'dotenv';
|
||
|
||
dotenv.config();
|
||
|
||
import authRoutes from './routes/auth';
|
||
import userRoutes from './routes/user';
|
||
import schemeRoutes from './routes/schemes';
|
||
import schemeAobRoutes from './routes/schemesAob';
|
||
import filterRoutes from './routes/filters';
|
||
import vipRoutes from './routes/vip';
|
||
import favoriteRoutes from './routes/favorites';
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 3001;
|
||
|
||
// ============================================
|
||
// 中间件
|
||
// ============================================
|
||
|
||
// 安全头 - 开发模式禁用 CSP
|
||
app.use(helmet({
|
||
contentSecurityPolicy: false,
|
||
crossOriginEmbedderPolicy: false,
|
||
}));
|
||
|
||
// CORS
|
||
app.use(cors({
|
||
origin: true, // 允许所有来源(开发模式)
|
||
credentials: true,
|
||
}));
|
||
|
||
// JSON 解析
|
||
app.use(express.json({ limit: '10mb' }));
|
||
app.use(express.urlencoded({ extended: true }));
|
||
|
||
// 速率限制
|
||
const limiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000, // 15 分钟
|
||
max: 100, // 每个 IP 最多 100 次请求
|
||
message: { success: false, message: '请求过于频繁,请稍后再试' },
|
||
});
|
||
app.use('/api/', limiter);
|
||
|
||
// 登录接口更严格的限制
|
||
const loginLimiter = rateLimit({
|
||
windowMs: 15 * 60 * 1000,
|
||
max: 10,
|
||
message: { success: false, message: '登录尝试过于频繁,请稍后再试' },
|
||
});
|
||
app.use('/api/login', loginLimiter);
|
||
app.use('/api/register', loginLimiter);
|
||
|
||
// ============================================
|
||
// 路由
|
||
// ============================================
|
||
|
||
app.get('/api/activity/ping', (req, res) => {
|
||
res.json({ success: true, message: 'pong', timestamp: Date.now() });
|
||
});
|
||
|
||
// 版本/健康检查 (用于验证部署)
|
||
app.get('/api/version', (req, res) => {
|
||
res.json({ version: 'd823421' });
|
||
});
|
||
|
||
app.use('/api', authRoutes);
|
||
app.use('/api/user', userRoutes);
|
||
app.use('/api/schemes', schemeRoutes);
|
||
app.use('/api/schemes_aob', schemeAobRoutes);
|
||
app.use('/api/filter-shares', filterRoutes);
|
||
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) {
|
||
res.json({ success: true, count: 0 });
|
||
}
|
||
});
|
||
|
||
app.use('/api/favorites', favoriteRoutes);
|
||
|
||
// ============================================
|
||
// 缺失的存根接口(防止前端 404)
|
||
// ============================================
|
||
|
||
// 二维码
|
||
app.get('/api/qrcode/public/current', (req, res) => {
|
||
res.json({ success: true, data: { qrcode: null, url: '' } });
|
||
});
|
||
|
||
// 弹窗 (通用参数化路由)
|
||
app.get('/api/popups/:name', (req, res) => {
|
||
const { name } = req.params;
|
||
const popups: Record<string, any> = {
|
||
test_beta_01: { id: 'test_beta_01', title: '测试弹窗', content: '', shown: false },
|
||
};
|
||
res.json({ success: true, data: popups[name] ?? null });
|
||
});
|
||
|
||
// 嘉豪弹窗
|
||
app.get('/api/jiahao', (req, res) => {
|
||
res.json({
|
||
success: true,
|
||
data: {
|
||
enabled: false,
|
||
message: '',
|
||
imageUrl: '',
|
||
},
|
||
});
|
||
});
|
||
|
||
// 售后教程弹窗
|
||
app.get('/api/aftersale-tutorial-popup', (req, res) => {
|
||
res.json({ success: true, data: { shown: false } });
|
||
});
|
||
|
||
// 软件版本广告(空数据,dock 无广告项)
|
||
app.get('/api/software-version-ad', (req, res) => {
|
||
res.json({ success: true, data: { items: [] } });
|
||
});
|
||
|
||
// 游戏地图密码缓存
|
||
app.get('/api/game/map-password/cached', (req, res) => {
|
||
res.json({ success: true, data: [] });
|
||
});
|
||
|
||
// 筛选分类
|
||
app.get('/api/filter-share/categories', (req, res) => {
|
||
res.json({ success: true, data: [] });
|
||
});
|
||
|
||
// ============================================
|
||
// 武器调谐窗 (weapon-tuner) 所需的 API
|
||
// ============================================
|
||
|
||
// 武器分类列表
|
||
app.get('/api/weapon-categories', (req, res) => {
|
||
const categories = [
|
||
{ category: 'AR', name: '突击步枪' },
|
||
{ category: 'SMG', name: '冲锋枪' },
|
||
{ category: 'SR', name: '狙击步枪' },
|
||
{ category: 'LMG', name: '轻机枪' },
|
||
{ category: 'SG', name: '霰弹枪' },
|
||
{ category: 'Pistol', name: '手枪' },
|
||
{ category: 'Launcher', name: '发射器' },
|
||
];
|
||
res.json({ success: true, data: categories });
|
||
});
|
||
|
||
// 武器列表(按分类筛选)
|
||
app.get('/api/weapons', (req, res) => {
|
||
res.json({ success: true, data: [] });
|
||
});
|
||
|
||
// 分类下的武器
|
||
app.get('/api/category/:code', (req, res) => {
|
||
res.json({ success: true, data: [] });
|
||
});
|
||
|
||
// 广告列表
|
||
app.get('/api/adverts/list', (req, res) => {
|
||
res.json({ success: true, data: [
|
||
{
|
||
id: 1,
|
||
title: '码枪堂2.0 新版发布',
|
||
description: '全新界面,更多功能,一键优化三角洲行动游戏体验!',
|
||
author: '码枪堂官方',
|
||
avatar: null,
|
||
image_url: '',
|
||
link_url: 'https://wwamt.lanzout.com/b00odpq4wb',
|
||
isAdvert: true,
|
||
isVip: true,
|
||
shareTime: new Date().toISOString(),
|
||
}
|
||
] });
|
||
});
|
||
|
||
// 广告点击
|
||
app.post('/api/adverts/:id/click', (req, res) => {
|
||
res.json({ success: true, message: 'ok' });
|
||
});
|
||
|
||
// 头像列表
|
||
app.get('/api/avatars', (req, res) => {
|
||
res.json({ success: true, data: [] });
|
||
});
|
||
|
||
// ============================================
|
||
// 更新服务 (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'
|
||
`);
|
||
});
|
||
|
||
// ============================================
|
||
// 错误处理
|
||
// ============================================
|
||
|
||
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||
console.error('Error:', err);
|
||
|
||
if (err.name === 'UnauthorizedError') {
|
||
return res.status(401).json({ success: false, message: '访问令牌无效', code: 'INVALID_TOKEN' });
|
||
}
|
||
|
||
if (err.name === 'ZodError') {
|
||
return res.status(400).json({ success: false, message: '参数验证失败', errors: err.errors });
|
||
}
|
||
|
||
res.status(500).json({ success: false, message: '服务器内部错误' });
|
||
});
|
||
|
||
// 404
|
||
app.use((req, res) => {
|
||
res.status(404).json({ success: false, message: '接口不存在' });
|
||
});
|
||
|
||
// ============================================
|
||
// 启动
|
||
// ============================================
|
||
|
||
app.listen(PORT, () => {
|
||
console.log(`🚀 码枪堂 API 运行在 http://localhost:${PORT}`);
|
||
console.log(`📋 可用接口:`);
|
||
console.log(` POST /api/login`);
|
||
console.log(` POST /api/register`);
|
||
console.log(` POST /api/activate-vip`);
|
||
console.log(` GET /api/session-status`);
|
||
console.log(` GET /api/vip-status`);
|
||
});
|
||
|
||
export default app; |