feat(HUD): 添加HUD界面资产与组件
- 新增HUD图标、面板和状态指示器资产 - 实现HUD图标组件和错误边界组件 - 重构顶部状态栏和底部导航栏 - 更新路线面板样式和交互 - 添加HUD资产清单和切片脚本 - 移除未使用的资产文件 - 调整API安全配置和生产环境设置
This commit is contained in:
@@ -8,13 +8,13 @@ export const registerAuthRoutes = (app: FastifyInstance, authService: AuthServic
|
||||
return { user };
|
||||
});
|
||||
|
||||
app.post('/api/auth/register', async (request, reply) => {
|
||||
app.post('/api/auth/register', { config: { rateLimit: { max: 5, timeWindow: '1 minute' } } }, async (request, reply) => {
|
||||
const input = registerSchema.parse(request.body);
|
||||
const user = await authService.register(input, reply);
|
||||
return { user };
|
||||
});
|
||||
|
||||
app.post('/api/auth/login', async (request, reply) => {
|
||||
app.post('/api/auth/login', { config: { rateLimit: { max: 10, timeWindow: '1 minute' } } }, async (request, reply) => {
|
||||
const input = loginSchema.parse(request.body);
|
||||
const user = await authService.login(input, reply);
|
||||
return { user };
|
||||
|
||||
@@ -48,7 +48,7 @@ export class AuthService {
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: SESSION_TTL_MS / 1000,
|
||||
secure: false,
|
||||
secure: env.isProduction,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,15 @@ export class GameRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async upsertMainSlot(userId: string, stateJson: string, label: string) {
|
||||
async upsertMainSlot(userId: string, stateJson: string, label: string, expectedRevision?: number) {
|
||||
const existing = await this.getMainSlot(userId);
|
||||
const now = Date.now();
|
||||
|
||||
if (existing) {
|
||||
if (expectedRevision !== undefined && existing.revision !== expectedRevision) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.db
|
||||
.update(saveSlotsTable)
|
||||
.set({
|
||||
|
||||
@@ -43,7 +43,10 @@ export class GameService {
|
||||
|
||||
const state = JSON.parse(slot.stateJson) as GameState;
|
||||
const result = applyGameAction(state, gameContent, action);
|
||||
await this.repository.upsertMainSlot(userId, JSON.stringify(result.state), slot.label);
|
||||
const saved = await this.repository.upsertMainSlot(userId, JSON.stringify(result.state), slot.label, slot.revision);
|
||||
if (!saved) {
|
||||
throw new AppError(409, 'SAVE_CONFLICT', '存档已被其他请求修改,请重试。');
|
||||
}
|
||||
return buildGameView(result.state, gameContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
||||
import Fastify from 'fastify';
|
||||
import cookie from '@fastify/cookie';
|
||||
import cors from '@fastify/cors';
|
||||
import rateLimit from '@fastify/rate-limit';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import { ZodError } from 'zod';
|
||||
import { db } from './shared/database/client';
|
||||
@@ -25,6 +26,11 @@ await app.register(cors, {
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
await app.register(rateLimit, {
|
||||
max: env.isProduction ? 100 : 1000,
|
||||
timeWindow: '1 minute',
|
||||
});
|
||||
|
||||
const authRepository = new AuthRepository(db);
|
||||
const authService = new AuthService(authRepository);
|
||||
const gameRepository = new GameRepository(db);
|
||||
|
||||
@@ -8,6 +8,7 @@ loadDotenv({
|
||||
});
|
||||
|
||||
const envSchema = z.object({
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
API_PORT: z.coerce.number().int().positive().default(3001),
|
||||
WEB_ORIGIN: z.string().url().default('http://localhost:5173'),
|
||||
WEB_DIST_DIR: z.string().min(1).default('../web/dist'),
|
||||
@@ -26,6 +27,8 @@ const webDistPath = resolve(process.cwd(), parsed.data.WEB_DIST_DIR);
|
||||
mkdirSync(dirname(databasePath), { recursive: true });
|
||||
|
||||
export const env = {
|
||||
nodeEnv: parsed.data.NODE_ENV,
|
||||
isProduction: parsed.data.NODE_ENV === 'production',
|
||||
port: parsed.data.API_PORT,
|
||||
webOrigin: parsed.data.WEB_ORIGIN,
|
||||
webDistPath,
|
||||
|
||||
Reference in New Issue
Block a user