diff --git a/Entity/Entity.csproj b/Entity/Entity.csproj
index c9ca112..398f5e7 100644
--- a/Entity/Entity.csproj
+++ b/Entity/Entity.csproj
@@ -10,6 +10,7 @@
+
diff --git a/Entity/Fantasy.config b/Entity/Fantasy.config
index eb56289..65ed1a7 100644
--- a/Entity/Fantasy.config
+++ b/Entity/Fantasy.config
@@ -57,13 +57,11 @@
networkProtocol=""
outerPort="0" innerPort="11031" />
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/Hotfix/Api/GameHttpApplicationHandler.cs b/Hotfix/Api/GameHttpApplicationHandler.cs
new file mode 100644
index 0000000..478d8e9
--- /dev/null
+++ b/Hotfix/Api/GameHttpApplicationHandler.cs
@@ -0,0 +1,66 @@
+using Fantasy.Async;
+using Fantasy.Event;
+using Fantasy.Network.HTTP;
+using Microsoft.AspNetCore.Builder;
+using System;
+using Fantasy;
+
+namespace NBF
+{
+ public class GameHttpApplicationHandler : AsyncEventSystem
+ {
+ protected override async FTask Handler(OnConfigureHttpApplication self)
+ {
+ var app = self.Application;
+
+ // 1. CORS(必须在认证之前)
+ app.UseCors("GameClient");
+
+ // 2. 请求日志中间件
+ app.Use(async (context, next) =>
+ {
+ var requestId = Guid.NewGuid().ToString("N");
+ context.Items["RequestId"] = requestId;
+
+ var start = DateTime.UtcNow;
+ var method = context.Request.Method;
+ var path = context.Request.Path;
+ var ip = context.Connection.RemoteIpAddress?.ToString();
+
+ Log.Info($"[HTTP-{requestId}] {method} {path} - IP: {ip}");
+
+ try
+ {
+ await next.Invoke();
+
+ var duration = (DateTime.UtcNow - start).TotalMilliseconds;
+ var status = context.Response.StatusCode;
+ Log.Info($"[HTTP-{requestId}] {status} - {duration}ms");
+ }
+ catch (Exception e)
+ {
+ Log.Error($"[HTTP-{requestId}] 异常: {e.Message}");
+ throw;
+ }
+ });
+
+ // 3. 认证
+ app.UseAuthentication();
+
+ // 4. 授权
+ app.UseAuthorization();
+
+ // 5. 自定义响应头
+ app.Use(async (context, next) =>
+ {
+ context.Response.Headers.Add("X-Game-Server", "Fantasy");
+ context.Response.Headers.Add("X-Server-Version", "1.0.0");
+ await next.Invoke();
+ });
+
+ Log.Info($"[HTTP] 游戏应用配置完成: Scene {self.Scene.SceneConfigId}");
+
+ await FTask.CompletedTask;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Hotfix/Api/GameHttpServicesHandler.cs b/Hotfix/Api/GameHttpServicesHandler.cs
new file mode 100644
index 0000000..2603b45
--- /dev/null
+++ b/Hotfix/Api/GameHttpServicesHandler.cs
@@ -0,0 +1,89 @@
+using Fantasy.Async;
+using Fantasy.Event;
+using Fantasy.Network.HTTP;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.IdentityModel.Tokens;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace NBF;
+
+public class GameHttpServicesHandler : AsyncEventSystem
+{
+ protected override async FTask Handler(OnConfigureHttpServices self)
+ {
+ // 1. 配置 JSON 序列化
+ self.MvcBuilder.AddJsonOptions(options =>
+ {
+ options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ options.JsonSerializerOptions.WriteIndented = true;
+ options.JsonSerializerOptions.DefaultIgnoreCondition =
+ JsonIgnoreCondition.WhenWritingNull;
+ options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
+ });
+
+ // 2. 添加全局过滤器
+ self.MvcBuilder.AddMvcOptions(options =>
+ {
+ options.Filters.Add();
+ options.Filters.Add();
+ });
+
+ // 3. 配置 JWT 认证
+ var jwtSecret = "YourSuperSecretKeyForJwtTokenGeneration123!";
+ self.Builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
+ .AddJwtBearer(options =>
+ {
+ options.TokenValidationParameters = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = true,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidIssuer = "GameServer",
+ ValidAudience = "GameClient",
+ IssuerSigningKey = new SymmetricSecurityKey(
+ Encoding.UTF8.GetBytes(jwtSecret)),
+ ClockSkew = TimeSpan.Zero
+ };
+ });
+
+ // 4. 配置授权策略
+ self.Builder.Services.AddAuthorization(options =>
+ {
+ options.AddPolicy("Player", policy =>
+ policy.RequireRole("Player", "Admin"));
+ options.AddPolicy("Admin", policy =>
+ policy.RequireRole("Admin"));
+ options.AddPolicy("VIP", policy =>
+ policy.RequireClaim("VIPLevel"));
+ });
+
+ // 5. 配置 CORS
+ self.Builder.Services.AddCors(options =>
+ {
+ options.AddPolicy("GameClient", builder =>
+ {
+ builder.WithOrigins(
+ "https://game.example.com",
+ "https://cdn.game.example.com"
+ )
+ .AllowAnyMethod()
+ .AllowAnyHeader()
+ .AllowCredentials();
+ });
+ });
+
+ // 6. 注册游戏服务
+ self.Builder.Services.AddSingleton();
+ self.Builder.Services.AddSingleton();
+ self.Builder.Services.AddScoped();
+ self.Builder.Services.AddScoped();
+
+ Log.Info($"[HTTP] 游戏服务配置完成: Scene {self.Scene.SceneConfigId}");
+
+ await FTask.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Hotfix/Api/PlayerController.cs b/Hotfix/Api/PlayerController.cs
new file mode 100644
index 0000000..d61f69b
--- /dev/null
+++ b/Hotfix/Api/PlayerController.cs
@@ -0,0 +1,89 @@
+using Fantasy;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+
+namespace NBF.Controllers
+{
+ [ApiController]
+ [Route("api/[controller]")]
+ public class PlayerController : ControllerBase
+ {
+ private readonly Scene _scene;
+ private readonly IPlayerService _playerService;
+
+ public PlayerController(Scene scene, IPlayerService playerService)
+ {
+ _scene = scene;
+ _playerService = playerService;
+ }
+
+ ///
+ /// 获取玩家信息(需要认证)
+ ///
+ [HttpGet("{playerId}")]
+ [Authorize(Policy = "Player")]
+ public async Task GetPlayer(long playerId)
+ {
+ var player = await _playerService.GetPlayerAsync(playerId);
+ if (player == null)
+ {
+ return NotFound(new { error = "Player not found" });
+ }
+
+ return Ok(new
+ {
+ playerId = player.Id,
+ name = player.Name,
+ level = player.Level,
+ exp = player.Exp
+ });
+ }
+
+ ///
+ /// 登录接口(无需认证)
+ ///
+ [HttpPost("login")]
+ [AllowAnonymous]
+ public async Task Login([FromBody] LoginRequest request)
+ {
+ var (success, token, playerId) = await _playerService.LoginAsync(
+ request.Username, request.Password);
+
+ if (!success)
+ {
+ return Unauthorized(new { error = "Invalid credentials" });
+ }
+
+ return Ok(new
+ {
+ token = token,
+ playerId = playerId,
+ expiresIn = 3600
+ });
+ }
+
+ ///
+ /// 管理员接口(需要 Admin 角色)
+ ///
+ [HttpPost("ban/{playerId}")]
+ [Authorize(Policy = "Admin")]
+ public async Task BanPlayer(long playerId, [FromBody] BanRequest request)
+ {
+ await _playerService.BanPlayerAsync(playerId, request.Reason, request.Duration);
+
+ return Ok(new { message = "Player banned successfully" });
+ }
+ }
+
+ public class LoginRequest
+ {
+ public string Username { get; set; }
+ public string Password { get; set; }
+ }
+
+ public class BanRequest
+ {
+ public string Reason { get; set; }
+ public int Duration { get; set; } // 分钟
+ }
+}
\ No newline at end of file
diff --git a/Hotfix/Api/PlayerService.cs b/Hotfix/Api/PlayerService.cs
new file mode 100644
index 0000000..5bfa052
--- /dev/null
+++ b/Hotfix/Api/PlayerService.cs
@@ -0,0 +1,6 @@
+namespace NBF;
+
+public class PlayerService : IPlayerService
+{
+
+}
\ No newline at end of file