T2FGame.Action
1.0.4
See the version list below for details.
dotnet add package T2FGame.Action --version 1.0.4
NuGet\Install-Package T2FGame.Action -Version 1.0.4
<PackageReference Include="T2FGame.Action" Version="1.0.4" />
<PackageVersion Include="T2FGame.Action" Version="1.0.4" />
<PackageReference Include="T2FGame.Action" />
paket add T2FGame.Action --version 1.0.4
#r "nuget: T2FGame.Action, 1.0.4"
#:package T2FGame.Action@1.0.4
#addin nuget:?package=T2FGame.Action&version=1.0.4
#tool nuget:?package=T2FGame.Action&version=1.0.4
T2FGame.Action
T2FGame 框架的 Action 处理层,提供类似 ioGame BarSkeleton 的业务处理框架。
功能特性
- Action 控制器:声明式路由配置,使用
[ActionController]和[ActionMethod]注解 - Pipeline 处理链:可扩展的请求处理管道,支持认证、授权、验证、缓存、限流等
- 参数绑定:自动解析 Protobuf 请求参数和 FlowContext 上下文
- 模块间调用:支持跨模块 Action 调用
- 消息推送:支持单播、多播、广播消息
- 调试输出:类似 ioGame 的 DebugInOut 控制台调试信息
- Result 模式集成:函数式错误处理
安装
<PackageReference Include="T2FGame.Action" />
快速开始
1. 定义 Action 控制器
[ActionController(cmd: 1)] // 模块命令
public class UserController
{
private readonly IUserService _userService;
public UserController(IUserService userService)
{
_userService = userService;
}
/// <summary>
/// 用户登录
/// </summary>
[ActionMethod(subCmd: 1)]
public async Task<LoginResponse> Login(LoginRequest request, FlowContext context)
{
var result = await _userService.LoginAsync(request.Username, request.Password);
// 设置认证状态
UserIdSettingKit.SettingUserId(context, result.UserId);
return new LoginResponse
{
UserId = result.UserId,
Token = result.Token
};
}
/// <summary>
/// 获取用户信息(需要认证)
/// </summary>
[ActionMethod(subCmd: 2, requireAuth: true)]
public async Task<UserInfo> GetUserInfo(FlowContext context)
{
var user = await _userService.GetByIdAsync(context.UserId);
return user.ToProto();
}
}
2. 注册服务
// 方式一:运行时反射扫描(无需 Source Generator)
services.AddT2FGameAction(typeof(UserController).Assembly);
// 方式二:编译时生成(需要引用 T2FGame.CodeGen.Action,零反射)
// 参见 "Source Generator 使用" 章节
services.AddT2FGameActionGenerated();
// 注册 Pipeline(可选配置)
services.AddActionPipeline(options =>
{
options.UseTraceHandler = true; // 链路追踪
options.UseAuthenticationHandler = true; // 认证检查
options.UseValidationHandler = true; // 参数验证
options.UseDebugInOutHandler = true; // 调试输出
});
Pipeline 处理链
请求流程:前置处理器 → Action 执行 → 后置处理器
内置处理器
| 处理器 | Order | 阶段 | 功能 | 默认启用 |
|---|---|---|---|---|
| TraceHandler | 10 | 前置 | 链路追踪,生成 TraceId | ✅ |
| TimeoutHandler | 15 | 前置 | 超时控制 | ❌ |
| RateLimitHandler | 20 | 前置 | 请求限流(滑动窗口) | ❌ |
| AuthenticationHandler | 30 | 前置 | 认证检查 | ✅ |
| AuthorizationHandler | 40 | 前置 | 授权检查(角色/权限) | ❌ |
| ValidationHandler | 50 | 前置 | 参数验证(DataAnnotations) | ✅ |
| CacheHandler | 60 | 前置 | 响应缓存检查 | ✅ |
| ActionInvokeHandler | 100 | 执行 | 执行 Action 方法 | ✅ |
| CacheStoreHandler | 200 | 后置 | 缓存存储 | ✅ |
| DebugInOutHandler | 205 | 后置 | 调试信息输出 | ❌ |
| LoggingHandler | 210 | 后置 | 日志记录 | ✅ |
| MetricsHandler | 220 | 后置 | 指标统计 | ❌ |
自定义处理器
处理器按 Order 属性从小到大依次执行,返回 false 将终止管道:
public class MyCustomHandler : ActionHandlerBase
{
// Order 值决定执行顺序:
// 10=Trace, 20=RateLimit, 30=Auth, 50=Validation, 60=Cache, 100=ActionInvoke
public override int Order => 25; // 在限流之后,认证之前
public override async ValueTask<bool> HandleAsync(ActionPipelineContext context)
{
// 前置逻辑
if (!CheckSomething(context))
{
context.SetError(ErrorCodes.Forbidden, "检查失败");
return false; // 中断管道
}
return true; // 继续执行下一个处理器
}
}
// 或使用接口
public class AnotherHandler : IActionHandler
{
public int Order => 35;
public string Name => "AnotherHandler";
public bool Enabled => true;
public async ValueTask<bool> HandleAsync(ActionPipelineContext context)
{
// 处理逻辑
return true;
}
}
// 注册自定义处理器
services.AddActionHandler<MyCustomHandler>();
参数验证
支持 .NET DataAnnotations 验证规范(类似 Java JSR 380):
public class RegisterRequest : IMessage<RegisterRequest>
{
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "不是一个合法的电子邮件地址")]
public string Email { get; set; }
[Required]
[StringLength(20, MinimumLength = 6, ErrorMessage = "密码长度必须在6-20之间")]
public string Password { get; set; }
[Range(18, 120, ErrorMessage = "年龄必须在18-120之间")]
public int Age { get; set; }
}
验证失败时自动返回错误码 202(ValidationError),不执行 Action 方法。
常用验证特性
| 特性 | 说明 |
|---|---|
[Required] |
必填 |
[EmailAddress] |
邮箱格式 |
[Range(min, max)] |
数值范围 |
[StringLength(max, MinimumLength)] |
字符串长度 |
[RegularExpression(pattern)] |
正则匹配 |
[Phone] |
电话格式 |
[Url] |
URL 格式 |
调试输出 (DebugInOut)
启用后在控制台输出请求处理详情,便于开发调试:
services.AddActionPipeline(options =>
{
options.UseDebugInOutHandler = true;
});
services.Configure<DebugInOutOptions>(options =>
{
options.Enabled = true;
options.MinTriggerTimeMs = 0; // 0=打印所有,>0=只打印慢请求
options.PrintRequestData = true; // 打印请求参数
options.PrintResponseData = true; // 打印响应数据
options.MaxDataLength = 200; // 最大数据长度
});
成功请求输出
┏━━━━━ Debug. [(UserController.cs).Login] ━━━━━ [1-1] ━━━━━
┣ userId: 12345
┣ sessionId: abc123
┣ 连接方式: WebSocket
┣ 参数: LoginRequest: { "username": "test" }
┣ 响应: LoginResponse: { "userId": 12345, "token": "xxx" }
┣ 时间: 5 ms (业务方法总耗时)
┗━━━━━ [T2FGame] ━━━━━ [线程:Worker-1] ━━━━━━━ [traceId:abc123def456] ━━━━━
错误请求输出
┏━━━━━ Error. [(UserController.cs).Register] ━━━━━ [1-2] ━━━━━
┣ userId: (未登录)
┣ sessionId: abc123
┣ 连接方式: WebSocket
┣ 参数: RegisterRequest: { "email": "invalid", "age": 10 }
┣ 错误码: 202
┣ 错误信息: 不是一个合法的电子邮件地址
┣ 时间: 0 ms (业务方法总耗时)
┗━━━━━ [T2FGame] ━━━━━ [线程:Worker-1] ━━━━━━━ [traceId:abc123def456] ━━━━━
忽略调试输出
[ActionController(cmd: 1)]
[IgnoreDebugInOut] // 整个控制器忽略
public class HeartbeatController
{
[ActionMethod(subCmd: 1)]
[IgnoreDebugInOut] // 单个方法忽略
public void Ping() { }
}
响应缓存
[ActionMethod(subCmd: 3)]
[ActionCache(Duration = 60, Scope = CacheScope.User, IncludeRequestData = true)]
public async Task<LeaderboardResponse> GetLeaderboard(FlowContext context)
{
// 此响应将被缓存 60 秒
return await _leaderboardService.GetTopPlayers();
}
| 参数 | 说明 |
|---|---|
Duration |
缓存时长(秒) |
Scope |
缓存范围:Global/User/Session |
IncludeRequestData |
是否将请求数据作为缓存键的一部分 |
限流
[ActionMethod(subCmd: 1)]
[RateLimit(Limit = 10, WindowSeconds = 60)] // 每分钟最多 10 次
public async Task<Response> SensitiveAction(FlowContext context)
{
// ...
}
消息广播
使用 FlowContext 内置广播方法(推荐)
[ActionController(cmd: 1)]
public class ChatController
{
[ActionMethod(subCmd: 1)]
public async Task SendMessage(ChatMessage request, FlowContext context)
{
var cmdInfo = new CmdInfo(1, 2);
var notify = new ChatNotify { Message = request.Content };
// 广播给所有人
await context.Broadcast(cmdInfo, notify);
// 广播给指定用户
await context.Broadcast(cmdInfo, notify, targetUserId);
// 广播给当前用户
await context.BroadcastMe(cmdInfo, notify);
// 房间广播
await context.BroadcastToRoom(cmdInfo, notify, roomId);
// 房间广播(排除自己)
await context.BroadcastToRoomExceptMe(cmdInfo, notify, roomId);
}
}
使用 RangeBroadcaster 精细控制
// 使用 RangeBroadcaster 进行精细控制
await RangeBroadcaster.Of(context)
.SetMessage(cmdInfo, notify)
.AddUserIds(1001, 1002, 1003) // 添加目标用户
.ExcludeUserId(context.UserId) // 排除发送者
.ExecuteAsync();
// 房间广播并排除用户
await RangeBroadcaster.Of(context)
.SetMessage(cmdInfo, notify)
.BroadcastToRoom("room_123")
.ExcludeUserIds(1001, 1002)
.ExecuteAsync();
模块间调用
[ActionController(cmd: 2)]
public class BattleController
{
[ActionMethod(subCmd: 1)]
public async Task<BattleStartResponse> StartBattle(
BattleStartRequest request,
FlowContext context)
{
// 调用用户模块获取玩家信息
var userInfo = await context.ModuleInvoker.InvokeAsync<UserInfo>(
CmdKit.GetMergeCmd(1, 2), // User 模块的 GetInfo
context);
var battle = await CreateBattle(userInfo, request);
return new BattleStartResponse { BattleId = battle.Id };
}
}
异常处理
内置异常
| 异常类型 | 错误码 | 说明 |
|---|---|---|
BusinessException |
自定义 | 业务异常 |
ValidationException |
202 | 验证失败 |
UnauthorizedAccessException |
100 | 未授权 |
TimeoutException |
501 | 超时 |
OperationCanceledException |
502 | 已取消 |
抛出业务异常
[ActionMethod(subCmd: 1)]
public async Task<Response> DoSomething(Request request, FlowContext context)
{
var item = await _repository.FindAsync(request.Id);
if (item == null)
{
throw new BusinessException(ErrorCodes.NotFound, "物品不存在");
}
// ...
}
自定义异常处理器
public class MyExceptionHandler : ISingleExceptionHandler
{
public int Order => 5;
public bool CanHandle(Exception exception) => exception is MyCustomException;
public ResponseMessage Handle(Exception exception, ActionPipelineContext context)
{
var ex = (MyCustomException)exception;
return ResponseMessage.Failure(context.ActionInfo.CmdMerge, ex.Code, ex.Message);
}
}
// 注册
services.AddExceptionHandler<MyExceptionHandler>();
Result 模式集成
[ActionMethod(subCmd: 1)]
public async Task<ResponseMessage> UpdateUserInfo(
UpdateUserRequest request,
FlowContext context)
{
// 业务层返回 Result<T>
Result<UserInfo> result = await _userService.UpdateAsync(context.UserId, request);
// 转换为 ResponseMessage
return result.ToResponseMessage(context.CmdMerge);
}
// 带转换函数
[ActionMethod(subCmd: 2)]
public async Task<ResponseMessage> GetProfile(FlowContext context)
{
Result<User> result = await _userService.GetByIdAsync(context.UserId);
return result.ToResponseMessage(
context.CmdMerge,
user => user.ToProfileProto());
}
错误码定义
public static class ErrorCodes
{
// 成功
public const int Success = 0;
// 通用错误 (1-99)
public const int Unknown = 1;
public const int InternalError = 2;
// 认证/授权错误 (100-199)
public const int Unauthorized = 100;
public const int Forbidden = 101;
public const int TokenExpired = 102;
// 请求错误 (200-299)
public const int InvalidRequest = 200;
public const int InvalidArgument = 201;
public const int ValidationError = 202;
public const int MissingParameter = 203;
// 资源错误 (300-399)
public const int NotFound = 300;
public const int AlreadyExists = 301;
// 状态错误 (400-499)
public const int InvalidOperation = 400;
public const int InvalidState = 401;
// 限流/超时 (500-599)
public const int RateLimited = 500;
public const int Timeout = 501;
public const int Cancelled = 502;
// 路由错误 (1000+)
public const int RouteNotFound = 1001;
public const int ActionNotFound = 1003;
}
目录结构
T2FGame.Action/
├── Attributes/
│ ├── ActionControllerAttribute.cs # 控制器注解
│ ├── ActionMethodAttribute.cs # 方法注解
│ ├── ActionCacheAttribute.cs # 缓存注解
│ ├── RateLimitAttribute.cs # 限流注解
│ └── IgnoreDebugInOutAttribute.cs # 忽略调试输出注解
├── Context/
│ ├── FlowContext.cs # 请求上下文基类
│ ├── ResponseMessage.cs # 响应消息
│ └── IModuleInvoker.cs # 模块调用接口
├── Extensions/
│ └── ResultExtensions.cs # Result 转换扩展
├── Invoker/
│ └── ModuleInvoker.cs # 模块调用实现
├── Pipeline/
│ ├── ActionPipeline.cs # 处理管道
│ ├── ActionPipelineContext.cs # 管道上下文
│ ├── IActionHandler.cs # 处理器接口
│ ├── IExceptionHandler.cs # 异常处理器
│ ├── PipelineServiceExtensions.cs # 服务注册
│ └── Handlers/
│ ├── TraceHandler.cs # 链路追踪
│ ├── TimeoutHandler.cs # 超时控制
│ ├── RateLimitHandler.cs # 限流处理
│ ├── AuthenticationHandler.cs # 认证处理
│ ├── AuthorizationHandler.cs # 授权处理
│ ├── ValidationHandler.cs # 参数验证
│ ├── CacheHandler.cs # 缓存处理
│ ├── ActionInvokeHandler.cs # Action 执行
│ ├── DebugInOutHandler.cs # 调试输出
│ └── LoggingHandler.cs # 日志/指标
├── Broadcast/
│ ├── IBroadcastService.cs # 广播服务接口
│ ├── IBroadcastOrderService.cs # 顺序广播服务接口
│ ├── IBroadcastRegistry.cs # 广播注册表接口
│ ├── ISessionManager.cs # 会话管理接口
│ ├── IRoomManager.cs # 房间管理接口
│ ├── RangeBroadcaster.cs # 范围广播工具
│ ├── BroadcastLogger.cs # 广播日志
│ └── BroadcastMessageAttribute.cs # 广播消息注解
├── Registry/
│ ├── ActionMethodRegistry.cs # Action 注册表
│ └── ActionMethodInfo.cs # Action 元信息
├── Utils/
│ └── UserIdSettingKit.cs # 用户 ID 设置工具
└── DependencyInjection.cs # 依赖注入扩展
Source Generator 使用
使用 T2FGame.CodeGen.Action Source Generator 可以消除运行时反射,提升性能:
添加引用
<ItemGroup>
<PackageReference Include="T2FGame.Action" />
<PackageReference Include="T2FGame.CodeGen.Action"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
使用生成的注册方法
// Source Generator 会在编译时生成 AddT2FGameActionGenerated 扩展方法
services.AddT2FGameActionGenerated();
services.AddActionPipeline();
生成的代码
编译后会在项目中生成以下文件:
GeneratedActionInvoker.g.cs- 零反射的方法调用器GeneratedActionRegistry.g.cs- 编译时生成的 Action 注册表GeneratedDependencyInjectionExtensions.g.cs-AddT2FGameActionGenerated()扩展方法
详细信息请参阅 T2FGame.CodeGen.Action README。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- Microsoft.Extensions.Caching.Memory (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- T2FGame.Core (>= 1.0.4)
- T2FGame.Protocol (>= 1.0.4)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on T2FGame.Action:
| Package | Downloads |
|---|---|
|
T2FGame
T2FGame Framework - A high-performance distributed game server framework inspired by ioGame. This meta-package includes all T2FGame components. |
|
|
T2FGame.Network.Socket
T2FGame Framework - High-performance Socket server based on SuperSocket (TCP/UDP/WebSocket) |
|
|
T2FGame.Cluster.Orleans
A high-performance game server framework |
|
|
T2FGame.Room
T2FGame Framework - Game room framework for multiplayer games, inspired by ioGame's RoomKit |
|
|
T2FGame.Cache.Redis
T2FGame Framework - Redis distributed cache implementation for high-performance caching |
GitHub repositories
This package is not used by any popular GitHub repositories.