diff --git a/Configurations/SimApiJobOptions.cs b/Configurations/SimApiJobOptions.cs index 62e0129..a0a47ac 100644 --- a/Configurations/SimApiJobOptions.cs +++ b/Configurations/SimApiJobOptions.cs @@ -29,11 +29,12 @@ public class SimApiJobOptions /// 设置为null 使用默认redis配置 /// public int? Database { get; set; } = null; + public SimApiJobServerConfig[] Servers { get; set; } = [new()]; } public class SimApiJobServerConfig() { public string[] Queues { get; set; } = ["default"]; - public int WorkerNum { get; set; } = 50; + public int WorkerNum { get; set; } = 5; } \ No newline at end of file diff --git a/README.md b/README.md index e490033..3f820ee 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,1070 @@ -# SimApi 基于.Net的一个基础辅助包 +# SimApi 使用说明书 -[![PublishNugetPackage](https://github.com/simcu/simapi-net/actions/workflows/nuget-publish.yml/badge.svg)](https://github.com/simcu/simapi-net/actions/workflows/nuget-publish.yml) -### 包含了以下组件: +> **NuGet 包名**:`Simcu.SimApi` +> **目标框架**:`net8.0` / `net9.0` / `net10.0` +> **作者**:xRain@SimcuTeam -1. 统一的参数检测,基础认证服务 -所有的控制器需要继承 YYApi.Controllers.BaseController +--- -```C# -using Models; -using SimApi.Controllers; +## 1. 项目概述 -namespace Controllers +SimApi 是一个基于 ASP.NET Core 的 **API 开发基础辅助库**,核心理念是**一行注册、一行启用**。通过 `AddSimApi()` + `UseSimApi()` 即可获得统一响应格式、认证、文档、对象存储、任务调度、MQTT 消息通信等全套能力。 + +### 主要功能特性 + +| 功能 | 说明 | +|------|------| +| **统一响应格式** | 自动封装所有接口响应为 `{code, message, data}` 格式 | +| **全局异常处理** | 捕获所有异常并以 JSON 格式返回,HTTP 状态码始终为 200 | +| **Token 认证** | 基于 Redis + Header Token 的简单认证机制 | +| **在线 API 文档** | 基于 Swagger 的多分组文档,含认证标注、签名参数自动注入 | +| **S3 对象存储** | MinIO 兼容的文件存储,支持预签名上传/下载 | +| **任务调度** | 基于 Hangfire + Redis 的后台任务管理,带 Web 控制台 | +| **MQTT 消息通信** | 基于 MQTTnet v5 的事件发布/订阅与 RPC 调用(Synapse 模块) | +| **API 签名验证** | MD5 签名 + 时间戳过期 + 防重放攻击 | +| **AES 加密传输** | 自动解密 AES-256-CBC 加密的请求体 | +| **Redis 缓存** | 统一封装的分布式缓存操作 | +| **Coce 统一身份** | 集成 Coce 第三方身份认证平台 | +| **彩色控制台日志** | 按级别着色的格式化控制台日志 | +| **CORS** | 开发/生产环境全量跨域支持 | +| **反向代理支持** | ForwardedHeaders 透传,获取真实 IP | + +--- + +## 2. 安装 + +```bash +dotnet add package Simcu.SimApi +``` + +--- + +## 3. 快速开始 + +`Program.cs` 中两步完成集成: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddSimApi(options => { - public class BaseController : SimApiBaseController + options.RedisConfiguration = "localhost:6379"; + options.EnableSimApiAuth = true; + options.EnableSimApiDoc = true; + + options.ConfigureSimApiDoc(doc => { - /// - /// 获取登录用户信息 - /// - protected SimApiLoginInfoItem LoginInfo => (SimApiLoginInfoItem) HttpContext.Items["LoginInfo"]; + doc.DocumentTitle = "我的API文档"; + doc.ApiGroups = + [ + new("api", "公共接口"), + new("admin", "管理接口", "需要管理员Token") + ]; + }); +}); + +var app = builder.Build(); +app.UseSimApi(); // 一行启用全部中间件和路由 +app.Run(); +``` + +--- + +## 4. 配置选项(SimApiOptions) + +所有配置通过 `AddSimApi(options => { ... })` 设置。 + +### 4.1 功能开关 + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `RedisConfiguration` | `string?` | `null` | Redis 连接字符串(多个模块依赖) | +| `EnableSimApiAuth` | `bool` | `false` | 启用 Token 认证体系 | +| `EnableSimApiDoc` | `bool` | `false` | 启用 Swagger 文档(`/swagger`) | +| `EnableSimApiStorage` | `bool` | `false` | 启用 S3 对象存储 | +| `EnableJob` | `bool` | `false` | 启用 Hangfire 任务调度 | +| `EnableSynapse` | `bool` | `false` | 启用 MQTT 消息/RPC 通信 | +| `EnableCoceSdk` | `bool` | `false` | 启用 Coce 统一身份平台 | +| `EnableCors` | `bool` | **`true`** | 启用 CORS 全部允许 | +| `EnableSimApiException` | `bool` | **`true`** | 启用全局异常拦截 | +| `EnableSimApiResponseFilter` | `bool` | **`true`** | 启用响应统一封装 | +| `EnableForwardHeaders` | `bool` | **`true`** | 启用反向代理 Header 转发 | +| `EnableLowerUrl` | `bool` | **`true`** | URL 强制小写 | +| `EnableVersionUrl` | `bool` | **`true`** | 启用 `/versions` 接口 | +| `EnableLogger` | `bool` | **`true`** | 启用彩色控制台日志 | + +### 4.2 子模块配置方法 + +```csharp +options.ConfigureSimApiDoc(doc => { ... }); +options.ConfigureSimApiStorage(s => { ... }); +options.ConfigureSimApiJob(job => { ... }); +options.ConfigureSimApiSynapse(s => { ... }); +options.ConfigureCoceSdk(coce => { ... }); +``` + +--- + +## 5. 核心模块 + +### 5.1 统一响应格式 + +#### 5.1.1 响应结构 + +所有接口统一返回以下 JSON 格式: + +```json +{ + "code": 200, + "message": "成功", + "data": { ... } +} +``` + +#### 5.1.2 状态码映射 + +| code | 默认 message | +|------|-------------| +| 200 | 成功 | +| 204 | 没有数据 | +| 400 | 参数错误 | +| 401 | 需要登录 | +| 403 | 无权访问 | +| 404 | 接口不存在 | +| 500 | 服务器错误 | + +#### 5.1.3 响应类 + +```csharp +// 无数据响应 +return new SimApiBaseResponse(); +return new SimApiBaseResponse(400, "参数错误"); +return new SimApiBaseResponse(404); // 自动映射消息:接口不存在 + +// 带数据响应 +return new SimApiBaseResponse(user); + +// 分页响应 +return new SimApiBaseResponse>>(new PageResponse> +{ + List = users, + Page = 1, + Count = 20, + Total = 100 +}); +``` + +#### 5.1.4 响应过滤器行为(SimApiResponseFilter) + +| 控制器返回值 | 最终 JSON 输出 | +|------------|--------------| +| `null` | `{"code":200,"message":"成功"}` | +| 普通对象 | `{"code":200,"message":"成功","data":对象}` | +| `SimApiBaseResponse` | 原样输出 | +| 标注 `[OriginResponse]` | 完全不封装,原始输出 | + +#### 5.1.5 跳过封装 + +```csharp +[HttpGet] +[OriginResponse] +public string GetRaw() => "raw string"; +``` + +--- + +### 5.2 基础控制器(SimApiBaseController) + +所有业务控制器建议继承 `SimApiBaseController`: + +```csharp +[ApiController] +[Route("[controller]")] +public class UserController : SimApiBaseController +{ + // 获取当前登录用户(需配合 EnableSimApiAuth) + // protected SimApiLoginItem LoginInfo => ... +} +``` + +#### 错误处理方法(均为 `protected static`) + +| 方法 | 触发条件 | 默认参数 | +|------|----------|---------| +| `Error(code, message)` | 直接抛出 | `500, ""` | +| `ErrorWhen(condition, code, message)` | condition == `true` | `400, ""` | +| `ErrorWhenTrue(condition, code, message)` | 同上(别名) | `400, ""` | +| `ErrorWhenFalse(condition, code, message)` | condition == `false` | `400, ""` | +| `ErrorWhenNull(obj, code, message)` | obj == `null` | `404, "请求的资源不存在"` | + +```csharp +public SimApiBaseResponse GetUser(int id) +{ + var user = _db.Users.Find(id); + ErrorWhenNull(user, 404, "用户不存在"); + + ErrorWhen(user.Age < 18, 403, "未满18岁"); + + return new SimApiBaseResponse(user); +} +``` + +--- + +### 5.3 认证模块(SimApiAuth) + +#### 5.3.1 配置 + +```csharp +options.EnableSimApiAuth = true; // 需同时配置 RedisConfiguration +``` + +#### 5.3.2 工作原理 + +1. 客户端请求时在 Header 中携带 `Token: ` +2. `SimApiAuthMiddleware` 从 Redis 读取登录信息存入 `HttpContext.Items["LoginInfo"]` +3. `[SimApiAuth]` Attribute 检查是否已登录及角色权限 + +#### 5.3.3 SimApiLoginItem 结构 + +```csharp +public class SimApiLoginItem +{ + public required string Id { get; set; } // 用户唯一标识 + public string[] Type { get; set; } = ["user"]; // 用户类型(支持多角色) + public Dictionary Meta { get; set; } = []; // 附加元数据 + public object? Extra { get; set; } // 扩展数据 +} +``` + +#### 5.3.4 SimApiAuth 助手方法 + +```csharp +// 注入使用 +public MyController(SimApiAuth auth) { ... } + +// 登录(token 不传则自动生成 GUID) +string token = auth.Login(loginItem); +string token = auth.Login(loginItem, "custom-token"); + +// 更新登录信息(不改变 token) +auth.Update(loginItem, token); + +// 获取登录信息 +SimApiLoginItem? info = auth.GetLogin(token); + +// 退出登录 +auth.Logout(token); +``` + +#### 5.3.5 自动注册路由 + +启用 `EnableSimApiAuth` 后自动生成以下路由: + +| 路由 | 方法 | 说明 | +|------|------|------| +| `POST /auth/check` | 无需登录 | 检测登录状态,返回用户 ID | +| `POST /auth/logout` | 无需登录 | 退出登录 | +| `POST /user/info` | 需要登录 | 获取当前用户完整信息 | + +--- + +### 5.4 特性(Attributes) + +#### `[SimApiAuth]` — 身份认证 + +```csharp +[SimApiAuth] // 仅检查是否登录 +[SimApiAuth("admin")] // 检查 Type 中是否含 "admin" +[SimApiAuth("admin,manager")] // 检查 Type 是否含 "admin" 或 "manager"(逗号分隔) +[SimApiAuth(new[]{"a", "b"})] // 数组形式 +``` + +#### `[SimApiDoc]` — Swagger 文档注解 + +```csharp +[SimApiDoc("用户管理", "获取用户列表")] +[SimApiDoc("用户管理", "创建用户", "创建一个新用户账号,需要管理员权限")] +[SimApiDoc(new[]{"用户", "管理"}, "批量操作")] +``` + +#### `[SimApiSign]` — API 签名验证 + +需配合实现 `SimApiSignProviderBase` 并注入: + +```csharp +[SimApiSign(KeyProvider = typeof(MySignProvider))] +public IActionResult SecureApi(...) { ... } +``` + +签名算法:`MD5(field1=v1&...&appId=xxx×tamp=ts&nonce=nnn&密钥)` + +#### `[AesBody]` — AES 加密请求体 + +客户端发送:`{"data": "AES密文"}`,服务端自动解密并反序列化: + +```csharp +[HttpPost] +public IActionResult Process([AesBody] MyRequest request) +{ + // request 已自动解密并反序列化 +} +``` + +#### `[OriginResponse]` — 跳过响应封装 + +```csharp +[HttpGet] +[OriginResponse] +public string GetRawData() => "raw data"; +``` + +#### `[SynapseEvent]` — MQTT 事件处理方法 + +```csharp +// 参数1:事件名;参数2(可选):事件数据 +[SynapseEvent("order/created")] +public void OnOrderCreated(string eventName) { ... } + +[SynapseEvent("order/+/status")] // 支持 MQTT 通配符 + 和 # +public void OnOrderStatus(string eventName, MyEventData data) { ... } +``` + +#### `[SynapseRpc]` — MQTT RPC 方法 + +```csharp +[SynapseRpc] // 方法名自动为 "ClassName.MethodName" +[SynapseRpc("getUserInfo")] // 自定义 RPC 方法名 + +// 0~2 个参数,第2个参数固定为 Dictionary(headers) +public UserDto GetUserInfo(GetUserRequest req) { ... } +public UserDto GetUserInfo(GetUserRequest req, Dictionary headers) { ... } +``` + +--- + +### 5.5 在线 API 文档(Swagger) + +#### 5.5.1 配置 + +```csharp +options.EnableSimApiDoc = true; +options.ConfigureSimApiDoc(doc => +{ + doc.DocumentTitle = "接口文档"; + + // 接口分组(Id 对应 ApiExplorerSettings.GroupName) + doc.ApiGroups = + [ + new("api", "公共接口"), + new("admin", "管理接口", "需要管理员Token") + ]; + + // 配置认证方式(支持 SimApiAuth / ClientCredentials / Implicit / AuthorizationCode / Password) + doc.ApiAuth = new SimApiAuthOption + { + Type = ["SimApiAuth"] // 默认 + }; + + // 支持的调试方法(默认仅 POST) + doc.SupportedMethod = [SubmitMethod.Post, SubmitMethod.Get]; +}); +``` + +#### 5.5.2 接口分组 + +```csharp +// 归入 admin 文档 +[ApiExplorerSettings(GroupName = "admin")] +public class AdminController : SimApiBaseController { ... } + +// 不标注则默认归入 Id="api" 的文档 +``` + +#### 5.5.3 访问文档 + +启动后访问 `/swagger`。 + +#### 5.5.4 自动 Swagger 增强 + +SimApi 内置多个 Swagger 过滤器,无需手动配置: + +| 过滤器 | 效果 | +|--------|------| +| `SimApiResponseOperationFilter` | 自动将返回类型包装为 `SimApiBaseResponse` 显示 | +| `SimApiAuthOperationFilter` | 为 `[SimApiAuth]` 接口添加 Token 认证要求 | +| `SimApiSignOperationFilter` | 为 `[SimApiSign]` 接口自动注入签名参数说明 | +| `AesBodyOperationFilter` | 为 `[AesBody]` 参数展示加密前的原始数据结构 | +| `GlobalDynamicObjectSchemaFilter` | 为 `object`/`Dictionary` 类型生成合理 Schema 示例 | +| `RemoveEmptyTagsFilter` | 清除没有接口的空分组 Tag | + +--- + +### 5.6 S3 对象存储(SimApiStorage) + +基于 MinIO SDK(S3 兼容协议)。 + +#### 5.6.1 配置 + +```csharp +options.EnableSimApiStorage = true; +options.ConfigureSimApiStorage(s => +{ + s.Endpoint = "http://minio.example.com:9000"; // 注意不能以 / 结尾 + s.AccessKey = "admin"; + s.SecretKey = "password"; + s.Bucket = "my-bucket"; + s.ServeUrl = "http://cdn.example.com/my-bucket"; // 注意不能以 / 结尾 +}); +``` + +#### 5.6.2 使用 + +```csharp +public MyController(SimApiStorage storage) { ... } + +// 获取预签名上传 URL(默认 2 小时过期),路径必须以 / 开头 +GetUploadUrlResponse r = storage.GetUploadUrl("/avatars/user1.jpg"); +// r.UploadUrl → 上传用的预签名 URL(供前端直接 PUT 请求) +// r.DownloadUrl → 下载用的公开 URL +// r.Path → 相对路径 + +// 获取预签名下载 URL(默认 10 分钟过期) +string url = storage.GetDownloadUrl("/files/doc.pdf"); +string url = storage.GetDownloadUrl("/files/doc.pdf", expire: 3600); + +// 直接服务端上传 +storage.UploadFile("/avatars/user1.jpg", stream, "image/jpeg"); + +// 路径转公开访问 URL +// - 以 http/https 开头:原样返回 +// - 以 ~/ 开头:转当前请求域名的本地路径 +// - 以 / 开头:拼接 ServeUrl +string? url = storage.FullUrl("/path/to/file"); +string? url = storage.GetUrl("/path/to/file"); + +// URL 转相对路径 +string? path = storage.GetPath("http://cdn.example.com/my-bucket/path/file"); + +// 暴露底层 MinIO 客户端 +IMinioClient mc = storage.Client; +``` + +--- + +### 5.7 任务调度(Hangfire) + +基于 Hangfire + Redis 存储。 + +#### 5.7.1 配置 + +```csharp +options.EnableJob = true; +options.ConfigureSimApiJob(job => +{ + job.DashboardUrl = "/jobs"; // null 则不启用 Dashboard + job.DashboardAuthUser = "admin"; + job.DashboardAuthPass = "Admin@123!"; + job.RedisConfiguration = null; // null 则使用全局 RedisConfiguration + job.Database = 1; // Redis DB 编号,null 则使用默认 + job.Servers = + [ + new SimApiJobServerConfig { Queues = ["default"], WorkerNum = 5 }, + new SimApiJobServerConfig { Queues = ["email"], WorkerNum = 2 } + ]; +}); +``` + +#### 5.7.2 使用 + +```csharp +// 立即执行 +BackgroundJob.Enqueue(() => Console.WriteLine("立即执行")); + +// 延迟执行 +BackgroundJob.Schedule(() => Console.WriteLine("延迟执行"), TimeSpan.FromMinutes(5)); + +// 定时循环执行 +RecurringJob.AddOrUpdate("daily-report", + () => myService.GenerateReport(), Cron.Daily); + +// 依赖执行(上一个完成后) +var jobId = BackgroundJob.Enqueue(() => Console.WriteLine("第一步")); +BackgroundJob.ContinueJobWith(jobId, () => Console.WriteLine("第二步")); +``` + +Dashboard 访问 `/jobs`,使用 HTTP Basic Auth(配置的用户名密码)。 + +--- + +### 5.8 MQTT 消息通信(Synapse) + +基于 MQTTnet v5,通过 WebSocket 连接 MQTT Broker。 + +#### 5.8.1 配置 + +```csharp +options.EnableSynapse = true; +options.ConfigureSimApiSynapse(s => +{ + s.Websocket = "ws://mqtt.example.com:8083/mqtt"; + s.Username = "user"; + s.Password = "pass"; + s.SysName = "my-system"; // 系统名(Topic 命名空间前缀) + s.AppName = "order-service"; // 服务名 + s.AppId = "instance-001"; // 实例 ID(不填则自动生成 GUID) + s.RpcTimeout = 3; // RPC 超时秒数,默认 3 + s.EventLoadBalancing = false; // 启用事件负载均衡($queue 订阅),默认 false + s.EnableConfigStore = true; // 启用分布式配置中心,默认 true + s.DisableEventClient = false; // 禁用事件客户端,默认 false + s.DisableRpcClient = false; // 禁用 RPC 客户端,默认 false +}); +``` + +#### 5.8.2 MQTT Topic 规则 + +| 用途 | Topic 格式 | +|------|-----------| +| 事件发布 | `{SysName}/event/{AppName}/{eventName}` | +| 事件订阅(负载均衡关闭) | `{SysName}/event/{eventName}` | +| 事件订阅(负载均衡开启) | `$queue/{SysName}/event/{eventName}` | +| RPC 请求 | `{SysName}/{targetApp}/rpc/server/{method}`(`$queue` 订阅,天然负载均衡) | +| RPC 响应 | `{SysName}/{callerApp}/rpc/client/{AppId}/{messageId}` | +| 配置存储 | `{SysName}/synapse-config-store/{key}`(Retain 消息) | + +#### 5.8.3 Synapse API(通过 DI 注入使用) + +```csharp +public MyService(Synapse synapse) { ... } + +// 发送事件 +synapse.Event("order/created", new { OrderId = 1 }); + +// 调用 RPC(同步阻塞,返回 SimApiBaseResponse) +var res = synapse.Rpc("user-service", "GetUserInfo", new { Id = 1 }); +var res = synapse.Rpc("user-service", "GetUserInfo", param, + headers: new Dictionary { { "traceId", "xxx" } }); + +// 无类型 RPC +var res = synapse.Rpc("user-service", "GetUserInfo", param); + +// 读写分布式配置 +synapse.SetConfig("max_retry", "3"); +string? value = synapse.GetConfig("max_retry"); + +// 监听配置变化 +synapse.OnConfigChanged += (sender, item) => + Console.WriteLine($"{item.Key} = {item.Value}"); + +// 在 RPC 方法内部抛出错误 +synapse.RpcError(400, "参数错误"); +synapse.RpcErrorWhen(id <= 0, 400, "ID 无效"); +``` + +#### 5.8.4 注册事件/RPC 处理器 + +启用 `EnableSynapse` 后,SimApi 会**自动扫描调用程序集**中所有含 `[SynapseRpc]` 或 `[SynapseEvent]` 方法的类,并自动注册为 Scoped 服务。 + +```csharp +// 事件处理类(无需手动注入,自动注册) +public class OrderEventHandler +{ + [SynapseEvent("order/+/status")] + public void OnOrderStatus(string eventName, OrderStatusDto data) + { + // eventName = "order/123/status" + // data 已自动反序列化 + } +} + +// RPC 服务类(无需手动注入,自动注册) +public class UserRpcService +{ + [SynapseRpc] // 方法名为 "UserRpcService.GetUserInfo" + public UserDto GetUserInfo(GetUserRequest req) + { + return new UserDto { ... }; + } + + [SynapseRpc("customRpcName")] + public ResultDto DoSomething(RequestDto req, Dictionary headers) + { + // headers 包含 RPC 调用方传入的自定义头 } } ``` +--- -统一配置,现在只需要进行AddSimApi的配置, Configure中直接 UseSimApi即可。 -```C# -services.AddSimApi(options => +### 5.9 API 签名验证(SimApiSign) + +#### 5.9.1 实现密钥提供者 + +```csharp +public class MySignProvider : SimApiSignProviderBase { - options.ConfigureSimApiDoc(options => - { - options.ApiGroups = new[] - { - new SimApiDocGroupOption - {Id = "admin", Name = "后台管理接口", Description = "本接口调用需要Scope:sac.api.admin"}, - new SimApiDocGroupOption - {Id = "user-v1", Name = "用户中心接口", Description = "本接口调用需要Scope:sac.api.user"} - }; - options.ApiAuth = new SimApiAuthOption - { - Type = new[] {"ClientCredentials", "Implicit", "AuthorizationCode"}, - Scopes = new Dictionary - { - {"sac.api.user", "用户信息接口权限"}, - {"sac.api.admin", "后台管理API"} - }, - AuthorizationUrl = "/connect/authorize", - TokenUrl = "/connect/token" - }; - }); - options.EnableSimApiStorage = true; - options.SimApiStorageOptions = Configuration.GetSection("S3").Get(); + private readonly IServiceScopeFactory _scopeFactory; + public MySignProvider(IServiceScopeFactory sf) => _scopeFactory = sf; + + public override string? AppIdName { get; set; } = "appId"; + public override string TimestampName { get; set; } = "timestamp"; + public override string NonceName { get; set; } = "nonce"; + public override string SignName { get; set; } = "sign"; + public override int QueryExpires { get; set; } = 5; // 5秒过期 + public override bool DuplicateRequestProtection { get; set; } = true; // 防重放 + public override string[] SignFields { get; set; } = ["userId"]; // 额外签名字段 + + public override string? GetKey(string? appId) + { + using var scope = _scopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + return db.Apps.Find(appId)?.SecretKey; + } +} + +// 注册(Scoped 或 Transient) +services.AddScoped(); +``` + +#### 5.9.2 使用 + +```csharp +[SimApiSign(KeyProvider = typeof(MySignProvider))] +public IActionResult SecureApi(...) { ... } +``` + +#### 5.9.3 签名算法 + +``` +MD5(field1=v1&field2=v2&...&appId=xxx×tamp=ts&nonce=nnn&密钥) +``` + +支持通过 Query 或 Header 传入签名参数。 + +--- + +### 5.10 AES 加密传输(AesBody) + +算法:**AES-256-CBC + PKCS7 填充**,IV 随机生成并附在密文前(Base64 编码)。 + +#### 5.10.1 实现密钥提供者 + +```csharp +public class MyAesKeyProvider : AesBodyProviderBase +{ + private readonly IServiceScopeFactory _scopeFactory; + public MyAesKeyProvider(IServiceScopeFactory sf) => _scopeFactory = sf; + + public override string? AppIdName { get; set; } = "appId"; // 从 Query/Header 获取 + + public override string? GetKey(string? appId) + { + using var scope = _scopeFactory.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + return db.Apps.Find(appId)?.SecretKey; + } +} + +// 注册 +services.AddScoped(); +``` + +#### 5.10.2 使用 + +```csharp +[HttpPost] +public IActionResult Submit([AesBody(KeyProvider = typeof(MyAesKeyProvider))] MyRequest request) +{ + // request 已自动解密并反序列化 +} +``` + +客户端请求格式(提交 JSON body): +```json +{"data": "Base64(AES加密后的JSON字符串)"} +``` + +#### 5.10.3 AES 工具类 + +```csharp +string cipher = SimApiAesUtil.Encrypt("明文内容", "任意长度密钥"); +string plain = SimApiAesUtil.Decrypt(cipher, "任意长度密钥"); +``` + +密钥会经过 SHA256 处理为 32 字节,因此支持任意长度密钥。 + +--- + +### 5.11 Redis 缓存(SimApiCache) + +统一加前缀 `SimApi:Cache:`,避免 key 冲突。 + +```csharp +public MyService(SimApiCache cache) { ... } + +// 存储(永不过期) +cache.Set("userCount", 100); + +// 存储(带过期时间) +cache.Set("userCount", 100, new DistributedCacheEntryOptions +{ + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) +}); + +// 获取原始字符串 +string? raw = cache.Get("userCount"); + +// 获取并反序列化为指定类型 +int? count = cache.Get("userCount"); +``` + +> 注意:`SimApiCache` 要求配置 `RedisConfiguration`。 + +--- + +### 5.12 HTTP 客户端(SimApiHttpClient) + +用于调用其他带签名/AES 加密的 SimApi 服务。 + +```csharp +var client = new SimApiHttpClient(appId: "myapp", appKey: "secret") +{ + Server = "https://api.example.com", + AppIdName = "appId", + TimestampName = "timestamp", + NonceName = "nonce", + SignName = "sign", + SignFields = ["field1", "field2"] +}; + +// 仅签名请求 +var result = client.SignQuery("/api/user", body, queries); + +// 仅 AES 加密请求(body 自动加密为 {"data":"Base64密文"}) +var result = client.AesQuery("/api/user", body); + +// AES 加密 + 签名 +var result = client.AesSignQuery("/api/user", body, queries); +``` + +--- + +### 5.13 工具类(SimApiUtil) + +全部为静态成员,直接调用,无需注入。 + +```csharp +// 时间 +DateTime cst = SimApiUtil.CstNow; // UTC+8 当前时间 +double timestamp = SimApiUtil.TimestampNow; // 秒级 Unix 时间戳 + +// 版本 +string simApiVer = SimApiUtil.SimApiVersion; // SimApi 包版本 +string appVer = SimApiUtil.AppVersion; // 宿主应用版本 + +// 哈希加密 +string md5 = SimApiUtil.Md5("source"); // 32位(mode="x2") +string md5 = SimApiUtil.Md5("source", "x3"); // 48位 +string sha1 = SimApiUtil.Sha1("source"); // SHA1 + +// 序列化 +string json = SimApiUtil.Json(obj); // camelCase,中文不转义 +T obj = SimApiUtil.XmlDeserialize(xml); // XML → 对象 +JsonSerializerOptions opts = SimApiUtil.JsonOption; // JSON 配置 + +// 验证 +bool ok = SimApiUtil.CheckCell("13800138000"); // 手机号格式验证 + +// 分页(IQueryable 扩展方法) +var paged = dbContext.Users.AsQueryable().Paginate(page: 1, count: 20); +``` + +--- + +### 5.14 数据模型基类(SimApiBaseModel) + +ORM 实体基类,提供通用字段和轻量级属性映射能力。 + +```csharp +public class UserEntity : SimApiBaseModel +{ + public string Name { get; set; } + // 自动拥有:Id (GUID, string)、CreatedAt、UpdatedAt +} + +// 从 DTO 映射到实体(自动跳过 Id / CreatedAt / UpdatedAt) +entity.MapData(dto); + +// 强制映射所有字段(包括 Id 等受保护字段) +entity.MapData(dto, mapAll: true); + +// 只映射指定字段 +entity.MapData(dto, new[] { "Name", "Email" }); + +// 手动更新 UpdatedAt +entity.UpdateTime(); +``` + +> **注意**:`MapData` 只映射**同名且同类型**且**源值不为 null** 的属性。 + +--- + +### 5.15 Coce 统一身份平台(CoceSdk) + +集成 Coce 第三方 OAuth 统一身份认证平台(默认 `api.coce.cc`)。 + +#### 5.15.1 配置 + +```csharp +options.EnableCoceSdk = true; // 需同时启用 EnableSimApiAuth +options.ConfigureCoceSdk(coce => +{ + coce.ApiEndpoint = "https://api.coce.cc"; // 默认值 + coce.AuthEndpoint = "https://home.coce.cc"; // 默认值 + coce.AppId = "your-app-id"; + coce.AppKey = "your-app-key"; }); ``` -基于S3的存储,基于RabbitMq的事件RPC调用,自定义Logger格式,自定义响应格式 + +#### 5.15.2 自动注册路由 + +| 路由 | 方法 | 说明 | +|------|------|------| +| `POST /auth/login` | 无需登录 | Coce 一键登录(前端传 `{"data":"lv1Token"}`) | +| `POST /user/groups` | 需要登录 | 获取当前用户群组列表 | +| `GET /auth/config` | 无需登录 | 获取 AppId 和授权 URL | + +#### 5.15.3 CoceApp 可用方法 + +```csharp +public MyService(CoceApp coce) { ... } + +// 用户 +coce.GetUserInfo(levelToken) // 获取用户基本信息 +coce.GetUserGroups(levelToken) // 获取用户群组 +coce.SearchUserByPhone("13800138000") // 按手机号搜索用户 +coce.SearchUserByIds(new[]{"uid1","uid2"}) // 按 ID 批量获取用户 + +// 消息 +coce.SendUserMessage(userId, "标题", "内容") + +// 支付 +string? tradeNo = coce.TradeCreate("商品名", 100, "扩展数据") // 创建订单 +coce.TradeCheck(tradeNo) // 查询订单状态 +coce.TradeRefund(tradeNo) // 发起退款 + +// Token 管理 +coce.GetLevelToken(lv1Token, level: 5) // 换取 Level Token +coce.SaveToken(userId, levelToken) // 保存到 Redis +coce.GetToken(userId) // 从 Redis 读取 + +// 代理请求 +coce.ProxyQuery(uri, token) +coce.ProxyQuery(uri, token, json) +coce.ProxyQueue(uri, token, data) +``` + +#### 5.15.4 自定义登录逻辑 + +实现 `ICoceLoginProcessor` 接口,在登录时根据群组赋予角色: + +```csharp +public class MyLoginProcessor : ICoceLoginProcessor +{ + public SimApiLoginItem Process(SimApiLoginItem item, GroupInfo[] groups) + { + if (groups.Any(g => g.Role == "owner")) + item.Type = ["user", "admin"]; + return item; + } +} + +// 注册 +services.AddScoped(); +``` + +--- + +### 5.16 日志(SimApiLogger) + +启用后替换默认控制台日志,输出格式如下: + +``` +[ Microsoft.AspNetCore.Hosting.Diagnostics ][ 2024-01-01 12:00:00:0000 ][ Information ] +Request starting HTTP/1.1 POST http://localhost/api/user +``` + +| 日志级别 | 控制台颜色 | +|----------|-----------| +| Debug | 深紫色 | +| Information | 深青色 | +| Warning | 黄色 | +| Error | 红色 | +| Critical | 深红色 | + +--- + +## 6. 内置路由汇总 + +| 路由 | 方法 | 启用条件 | 说明 | +|------|------|----------|------| +| `/swagger` | GET | `EnableSimApiDoc` | API 文档页面 | +| `/versions` | GET/POST | `EnableVersionUrl` | 查看版本信息 | +| `/auth/check` | POST | `EnableSimApiAuth` | 检测登录状态 | +| `/auth/logout` | POST | `EnableSimApiAuth` | 退出登录 | +| `/user/info` | POST | `EnableSimApiAuth` | 获取用户信息(需登录) | +| `/auth/login` | POST | `EnableCoceSdk` | Coce 登录 | +| `/user/groups` | POST | `EnableCoceSdk` | 获取用户群组(需登录) | +| `/auth/config` | POST | `EnableCoceSdk` | 获取 Coce 配置 | +| `/jobs` | GET | `EnableJob` | Hangfire Dashboard | + +--- + +## 7. 异常处理机制 + +``` +HTTP 请求 + ↓ +SimApiExceptionMiddleware(最外层,透传 Query-Id Header) + ├── 捕获 SimApiException → 返回 {code: xxx, message: xxx} + ├── 捕获 Exception → 记录日志 + 返回 {code: 500, message: 错误信息} + └── 响应 404 等非 200/301/302 状态码 → 转换为 SimApiException + ↓ +SimApiAuthMiddleware(解析 Token → HttpContext.Items["LoginInfo"]) + ↓ +[SimApiSign] Filter(签名验证) + ↓ +[SimApiAuth] Filter(登录/角色检查) + ↓ +Controller.OnActionExecuting(模型验证 → 400) + ↓ +Action 方法执行 + ↓ +SimApiResponseFilter(自动封装响应) +``` + +> **关键特性**:所有错误均以 **HTTP 200** 状态码返回,错误信息体现在响应 JSON 的 `code` 字段中。 + +--- + +## 8. 完整配置示例 + +```csharp +builder.Services.AddSimApi(options => +{ + // Redis(多功能共享) + options.RedisConfiguration = "localhost:6379"; + + // 认证 + options.EnableSimApiAuth = true; + + // Swagger 文档 + options.EnableSimApiDoc = true; + options.ConfigureSimApiDoc(doc => + { + doc.DocumentTitle = "我的服务接口文档"; + doc.ApiGroups = + [ + new("api", "公共接口"), + new("admin", "管理接口", "需要管理员 Token") + ]; + doc.ApiAuth = new SimApiAuthOption { Type = ["SimApiAuth"] }; + doc.SupportedMethod = [SubmitMethod.Post]; + }); + + // S3 存储 + options.EnableSimApiStorage = true; + options.ConfigureSimApiStorage(s => + { + s.Endpoint = "http://minio:9000"; + s.AccessKey = "admin"; + s.SecretKey = "password"; + s.Bucket = "my-bucket"; + s.ServeUrl = "http://cdn.example.com/my-bucket"; + }); + + // 任务调度 + options.EnableJob = true; + options.ConfigureSimApiJob(job => + { + job.DashboardUrl = "/jobs"; + job.DashboardAuthUser = "admin"; + job.DashboardAuthPass = "Admin@123!"; + job.Servers = [new() { Queues = ["default"], WorkerNum = 5 }]; + }); + + // MQTT 通信 + options.EnableSynapse = true; + options.ConfigureSimApiSynapse(s => + { + s.Websocket = "ws://mqtt:8083/mqtt"; + s.Username = "user"; + s.Password = "pass"; + s.SysName = "my-system"; + s.AppName = "api-service"; + }); + + // Coce 统一身份 + options.EnableCoceSdk = true; + options.ConfigureCoceSdk(coce => + { + coce.AppId = "your-app-id"; + coce.AppKey = "your-app-key"; + }); +}); + +var app = builder.Build(); +app.UseSimApi(); +app.Run(); +``` + +--- + +## 9. 项目依赖 + +| 包 | 版本 | 用途 | +|----|------|------| +| `Hangfire.AspNetCore` | 1.8.22 | 任务调度框架 | +| `Hangfire.Console` | 1.4.3 | 任务控制台日志 | +| `Hangfire.Redis.StackExchange` | 1.12.0 | Hangfire Redis 存储 | +| `Microsoft.Extensions.Caching.StackExchangeRedis` | 10.0.1 | Redis 分布式缓存 | +| `Minio` | 7.0.0 | S3 对象存储 | +| `MQTTnet` | 5.0.1.1416 | MQTT 消息通信 | +| `Swashbuckle.AspNetCore.Annotations` | 10.1.0 | Swagger 注解 | +| `Swashbuckle.AspNetCore.SwaggerUI` | 10.1.0 | Swagger UI | + +--- + +## 10. 最佳实践 + +### 10.1 控制器设计 + +- 所有控制器继承 `SimApiBaseController` +- 优先使用 `ErrorWhenNull` / `ErrorWhen` 系列方法进行前置校验,保持主逻辑清晰 +- 需要认证的接口标注 `[SimApiAuth]`,按角色访问控制的传入角色参数 +- 用 `[SimApiDoc]` 为每个接口添加文档注解,分组管理 + +### 10.2 存储管理 + +- 路径统一以 `/` 开头,按业务分类规划路径结构(如 `/avatars/{userId}/`) +- 对公开资源使用 `GetUrl`,对私密资源使用 `GetDownloadUrl`(设合适过期时间) +- 上传前在业务层验证文件类型和大小 + +### 10.3 任务调度 + +- 将长耗时操作异步化,接口立即返回,后台任务处理 +- 根据任务类型设置不同队列,避免低优先级任务阻塞高优先级任务 +- 定期检查 Hangfire Dashboard,监控失败任务 + +### 10.4 Synapse 消息通信 + +- 事件名使用层级路径风格(如 `order/created`、`payment/success`) +- RPC 方法名使用简洁的语义名称 +- 为幂等性操作设计事件处理器(同一事件可能被多次投递) +- 超时处理:`Rpc` 返回 `code=502` 表示超时 + +### 10.5 安全 + +- Token 认证默认不设过期时间,建议在业务层配合定期清理或设置 Redis TTL +- 签名验证默认开启防重放(5 秒 nonce 缓存),生产环境保持开启 +- AES 密钥通过数据库存储,不硬编码在代码中 diff --git a/SimApi使用说明书.md b/SimApi使用说明书.md deleted file mode 100644 index 9097cbd..0000000 --- a/SimApi使用说明书.md +++ /dev/null @@ -1,796 +0,0 @@ -# SimApi 库使用说明书 - -## 1. 项目概述 - -SimApi 是一个基于 .NET 的基础辅助包,提供了一系列实用功能,帮助开发者快速构建和部署 API 服务。 - -### 主要功能特性: - -- **统一的参数检测和错误处理**:自动验证请求参数并返回标准化的错误响应 -- **基础认证服务**:基于 Header Token 的简单认证机制 -- **S3 兼容的存储系统**:支持文件上传、下载和管理 -- **任务调度系统**:基于 Hangfire 的后台任务管理 -- **事件和 RPC 调用**:基于 RabbitMQ 的事件和 RPC 通信 -- **自定义日志格式**:提供格式化的控制台日志 -- **在线 API 文档**:基于 Swagger 的 API 文档生成 -- **统一的响应格式**:标准化的 API 响应结构 -- **CORS 配置**:支持跨域资源共享 -- **版本管理**:提供应用版本和 SimApi 版本查询 - -## 2. 安装方法 - -### 通过 NuGet 安装: - -```bash -Install-Package SimApi -``` - -### 项目集成 - -在 `Startup.cs` 或 `Program.cs` 中配置 SimApi: - -```csharp -// 在 ConfigureServices 方法中 -services.AddSimApi(options => -{ - // 配置选项 -}); - -// 在 Configure 方法中 -app.UseSimApi(); -``` - -## 3. 核心功能模块 - -### 3.1 基础控制器 - -所有控制器应继承自 `SimApiBaseController`,以获得统一的参数检测和错误处理功能。 - -```csharp -using SimApi.Controllers; - -public class BaseController : SimApiBaseController -{ - /// - /// 获取登录用户信息 - /// - protected SimApiLoginItem LoginInfo => (SimApiLoginItem) HttpContext.Items["LoginInfo"]; -} -``` - -### 3.2 认证服务 - -#### 配置认证服务: - -```csharp -services.AddSimApi(options => -{ - options.EnableSimApiAuth = true; -}); -``` - -#### 使用认证: - -1. 在控制器或动作方法上添加 `[SimApiAuth]` 属性 -2. 登录用户信息可通过 `LoginInfo` 属性获取 - -#### 认证相关接口: - -- `POST /auth/check`:检测用户登录状态 -- `POST /auth/logout`:用户退出登录 -- `POST /user/info`:获取用户信息 - -### 3.3 存储服务 - -#### 配置存储服务: - -```csharp -services.AddSimApi(options => -{ - options.EnableSimApiStorage = true; - options.SimApiStorageOptions = Configuration.GetSection("S3").Get(); -}); -``` - -#### 存储配置选项: - -```json -{ - "S3": { - "Endpoint": "http://localhost:9000", - "AccessKey": "minioadmin", - "SecretKey": "minioadmin", - "Bucket": "mybucket", - "ServeUrl": "http://localhost:9000/mybucket" - } -} -``` - -#### 使用存储服务: - -```csharp -private readonly SimApiStorage _storage; - -public MyController(SimApiStorage storage) -{ - _storage = storage; -} - -// 获取上传 URL -var uploadUrlResponse = _storage.GetUploadUrl("/path/to/file.txt"); - -// 获取下载 URL -var downloadUrl = _storage.GetDownloadUrl("/path/to/file.txt"); - -// 直接上传文件 -using var stream = new MemoryStream(); -_storage.UploadFile("/path/to/file.txt", stream, "text/plain"); - -// 获取完整访问 URL -var fullUrl = _storage.FullUrl("/path/to/file.txt"); -``` - -### 3.4 任务调度系统 - -#### 配置任务调度: - -```csharp -services.AddSimApi(options => -{ - options.EnableJob = true; - options.SimApiJobOptions = new SimApiJobOptions - { - DashboardUrl = "/jobs", - DashboardAuthUser = "admin", - DashboardAuthPass = "Admin@123!", - RedisConfiguration = "localhost:6379", - Servers = new[] - { - new SimApiJobServerConfig - { - Queues = new[] { "default" }, - WorkerNum = 50 - } - } - }; -}); -``` - -#### 使用任务调度: - -```csharp -// 立即执行任务 -BackgroundJob.Enqueue(() => Console.WriteLine("Hello, world!")); - -// 延迟执行任务 -BackgroundJob.Schedule(() => Console.WriteLine("Delayed job"), TimeSpan.FromMinutes(1)); - -// 重复执行任务 -RecurringJob.AddOrUpdate("my-recurring-job", () => Console.WriteLine("Recurring job"), Cron.Hourly); - -// 连续执行任务 -var id = BackgroundJob.Enqueue(() => Console.WriteLine("First job")); -BackgroundJob.ContinueWith(id, () => Console.WriteLine("Second job")); -``` - -### 3.5 事件和 RPC 调用 - -#### 配置事件和 RPC: - -```csharp -services.AddSimApi(options => -{ - options.EnableSynapse = true; - options.SimApiSynapseOptions = new SimApiSynapseOptions - { - // 配置选项 - }; -}); -``` - -#### 使用事件: - -```csharp -// 发布事件 -var synapse = serviceProvider.GetRequiredService(); -synapse.PublishEvent("event-name", data); - -// 订阅事件 -[SynapseEvent("event-name")] -public void HandleEvent(dynamic data) -{ - // 处理事件 -} -``` - -#### 使用 RPC: - -```csharp -// 发布 RPC 调用 -var result = await synapse.CallRpcAsync("rpc-method", data); - -// 实现 RPC 方法 -[SynapseRpc("rpc-method")] -public string GetData(dynamic data) -{ - return "Hello, RPC!"; -} -``` - -### 3.6 在线 API 文档 - -#### 配置 API 文档: - -```csharp -services.AddSimApi(options => -{ - options.EnableSimApiDoc = true; - options.ConfigureSimApiDoc(docOptions => - { - docOptions.ApiGroups = new[] - { - new SimApiDocGroupOption - { - Id = "admin", - Name = "后台管理接口", - Description = "本接口调用需要Scope:sac.api.admin" - }, - new SimApiDocGroupOption - { - Id = "user-v1", - Name = "用户中心接口", - Description = "本接口调用需要Scope:sac.api.user" - } - }; - docOptions.ApiAuth = new SimApiAuthOption - { - Type = new[] { "ClientCredentials", "Implicit", "AuthorizationCode" }, - Scopes = new Dictionary - { - { "sac.api.user", "用户信息接口权限" }, - { "sac.api.admin", "后台管理API" } - }, - AuthorizationUrl = "/connect/authorize", - TokenUrl = "/connect/token" - }; - }); -}); -``` - -#### 访问 API 文档: - -启动应用后,访问 `/swagger` 查看 API 文档。 - -### 3.7 统一响应格式 - -#### 配置响应过滤器: - -```csharp -services.AddSimApi(options => -{ - options.EnableSimApiResponseFilter = true; -}); -``` - -#### 响应过滤器实现: - -SimApi 提供了 `SimApiResponseFilter` 结果过滤器,用于自动封装 API 响应为统一格式: - -- 自动将 `null` 结果封装为 `{"Code": 200, "Message": "成功"}` -- 自动将普通对象结果封装为 `{"Code": 200, "Message": "成功", "Data": 对象}` -- 自动将 `EmptyResult` 封装为 `{"Code": 200, "Message": "成功"}` -- 保持 `SimApiBaseResponse` 类型的结果不变 - -#### 异常中间件: - -SimApi 还提供了 `SimApiExceptionMiddleware` 异常中间件,用于统一处理异常: - -- 捕获所有未处理的异常 -- 将异常转换为标准化的错误响应格式 -- 处理 HTTP 状态码,如 404 等 -- 记录错误日志 - -#### 使用响应格式: - -```csharp -// 无数据响应 -return new SimApiBaseResponse(); - -// 带数据响应 -return new SimApiBaseResponse(user); - -// 直接返回对象,会自动被封装 -return user; - -// 错误响应 -Error(400, "参数错误"); - -// 条件错误检查 -ErrorWhenNull(user, 404, "用户不存在"); -ErrorWhen(user.Age < 18, 403, "未满18岁,无权访问"); -``` - -#### 原始响应标记: - -如果需要返回原始响应格式,不使用统一封装,可以在控制器或动作方法上添加 `[OriginResponse]` 属性: - -```csharp -[HttpGet] -[OriginResponse] // 返回原始响应格式 -public string GetRawData() -{ - return "原始字符串响应"; -} -``` - -## 4. API 参考 - -### 4.1 核心类 - -#### SimApiUtil - -**命名空间**:`SimApi.Helpers` - -**描述**:提供一系列静态工具方法和属性,用于常见操作。 - -**主要属性**: - -- `CstNow`:获取当前 CST(中国标准时间) -- `JsonOption`:JSON 序列化常规选项 -- `SimApiVersion`:获取 SimApi 库版本 -- `AppVersion`:获取应用版本 -- `TimestampNow`:获取当前秒级时间戳 - -**主要方法**: - -- `CheckCell(string cell)`:检测手机号是否正确 -- `Md5(string source, string mode = "x2")`:MD5 加密字符串 -- `Sha1(string source, string mode = "x2")`:SHA1 加密字符串 -- `XmlDeserialize(string source)`:将 XML 字符串序列化为对象 -- `Json(object? obj)`:将对象序列化为 JSON 字符串 -- `Paginate(this IQueryable query, int page, int count)`:分页扩展方法 - -**使用示例**: - -```csharp -// 获取当前时间 -var now = SimApiUtil.CstNow; - -// JSON 序列化 -var json = SimApiUtil.Json(new { Name = "Test", Age = 18 }); - -// MD5 加密 -var md5 = SimApiUtil.Md5("password"); - -// 分页 -var query = dbContext.Users.AsQueryable(); -var paginatedQuery = query.Paginate(1, 10); - -// 获取版本信息 -var simApiVersion = SimApiUtil.SimApiVersion; -var appVersion = SimApiUtil.AppVersion; -``` - -#### SimApiExtensions - -**命名空间**:`SimApi` - -**描述**:提供一系列扩展方法,用于配置和使用 SimApi。 - -**主要方法**: - -- `AddSimApi(this IServiceCollection builder, Action? options = null)`:向服务集合添加 SimApi 服务和配置 -- `UseSimApi(this IHost builder)`:在主机上使用 SimApi -- `UseSimApi(this WebApplication builder)`:在 Web 应用上使用 SimApi,配置中间件和路由 - -**使用示例**: - -```csharp -// 在 ConfigureServices 方法中 -services.AddSimApi(options => -{ - // 配置选项 - options.EnableSimApiDoc = true; - options.EnableSimApiAuth = true; - // 其他配置... -}); - -// 在 Configure 方法中 -app.UseSimApi(); -``` - -#### SimApiBaseController - -**继承自**:`Controller` - -**主要方法**: - -- `Error(int code = 500, string message = "")`:抛出错误异常 -- `ErrorWhen(bool condition, int code = 400, string message = "")`:当条件为真时抛出错误 -- `ErrorWhenNull(object? condition, int code = 404, string message = "请求的资源不存在")`:当对象为 null 时抛出错误 -- `UploadFile()`:上传文件 - -**属性**: - -- `LoginInfo`:获取当前登录用户信息 - -#### SimApiAuth - -**主要方法**: - -- `Login(SimApiLoginItem loginItem, string? token = null)`:登录用户并返回 token -- `Update(SimApiLoginItem loginItem, string token)`:更新用户登录信息 -- `GetLogin(string token)`:根据 token 获取登录信息 -- `Logout(string uuid)`:退出登录 - -#### SimApiStorage - -**主要方法**: - -- `GetUploadUrl(string path, int expire = 7200)`:获取文件上传 URL -- `GetDownloadUrl(string path, int expire = 600)`:获取文件下载 URL -- `UploadFile(string path, Stream stream, string contentType = "image/png")`:上传文件 -- `FullUrl(string? path)`:获取完整的文件访问 URL -- `GetUrl(string? path)`:获取文件访问 URL -- `GetPath(string? url)`:从 URL 中获取相对路径 - -#### SimApiBaseResponse - -**构造函数**: - -- `SimApiBaseResponse(int code = 200, string message = "成功")`:创建响应对象 - -**属性**: - -- `Code`:响应代码 -- `Message`:响应消息 - -#### SimApiBaseResponse - -**继承自**:`SimApiBaseResponse` - -**构造函数**: - -- `SimApiBaseResponse(T data)`:创建带数据的响应对象 - -**属性**: - -- `Data`:响应数据 - -### 4.2 配置类 - -#### SimApiOptions - -**主要属性**: - -- `RedisConfiguration`:Redis 配置字符串 -- `EnableJob`:是否启用任务调度系统 -- `EnableSimApiAuth`:是否启用认证服务 -- `EnableCoceSdk`:是否启用 CoceSdk -- `EnableSimApiStorage`:是否启用存储服务 -- `EnableSimApiDoc`:是否启用 API 文档 -- `EnableSynapse`:是否启用事件和 RPC -- `EnableCors`:是否启用 CORS -- `EnableSimApiException`:是否启用异常拦截 -- `EnableSimApiResponseFilter`:是否启用响应过滤器 -- `EnableForwardHeaders`:是否启用 Header 转发 -- `EnableLowerUrl`:是否启用小写 URL -- `EnableVersionUrl`:是否启用版本查询 -- `EnableLogger`:是否启用自定义日志 - -**配置方法**: - -- `ConfigureSimApiDoc(Action? options = null)`:配置 API 文档 -- `ConfigureSimApiStorage(Action? options = null)`:配置存储服务 -- `ConfigureSimApiJob(Action? options = null)`:配置任务调度 -- `ConfigureSimApiSynapse(Action? options = null)`:配置事件和 RPC -- `ConfigureCoceSdk(Action? options = null)`:配置 CoceSdk - -## 5. 配置选项 - -### 5.1 存储配置 (SimApiStorageOptions) - -```csharp -public class SimApiStorageOptions -{ - public string? Endpoint { get; set; } // S3 服务端点 - public string? AccessKey { get; set; } // 访问密钥 - public string? SecretKey { get; set; } // 密钥 - public string? Bucket { get; set; } // 存储桶名称 - public string? ServeUrl { get; set; } // 访问 URL -} -``` - -### 5.2 任务调度配置 (SimApiJobOptions) - -```csharp -public class SimApiJobOptions -{ - public string? DashboardUrl { get; set; } = "/jobs"; // Web UI 地址 - public string DashboardAuthUser { get; set; } = "admin"; // Web UI 用户名 - public string DashboardAuthPass { get; set; } = "Admin@123!"; // Web UI 密码 - public string? RedisConfiguration { get; set; } // Redis 配置 - public int? Database { get; set; } = null; // Redis 数据库 - public SimApiJobServerConfig[] Servers { get; set; } = [new()]; // 服务器配置 -} - -public class SimApiJobServerConfig -{ - public string[] Queues { get; set; } = ["default"]; // 队列名称 - public int WorkerNum { get; set; } = 50; // 工作线程数 -} -``` - -### 5.3 API 文档配置 (SimApiDocOptions) - -```csharp -public class SimApiDocOptions -{ - public string DocumentTitle { get; set; } = "API 文档"; // 文档标题 - public SimApiDocGroupOption[] ApiGroups { get; set; } = []; // API 分组 - public SimApiAuthOption ApiAuth { get; set; } = new(); // 认证配置 - public string[] SupportedMethod { get; set; } = ["GET", "POST", "PUT", "DELETE"]; // 支持的 HTTP 方法 -} - -public class SimApiDocGroupOption -{ - public string Id { get; set; } = "api"; // 分组 ID - public string Name { get; set; } = "API"; // 分组名称 - public string Description { get; set; } = ""; // 分组描述 -} - -public class SimApiAuthOption -{ - public string[] Type { get; set; } = []; // 认证类型 - public Dictionary Scopes { get; set; } = []; // 权限范围 - public string AuthorizationUrl { get; set; } = "/connect/authorize"; // 授权 URL - public string TokenUrl { get; set; } = "/connect/token"; // Token URL - public string Description { get; set; } = ""; // 认证描述 -} -``` - -## 6. 使用示例 - -### 6.1 完整配置示例 - -```csharp -services.AddSimApi(options => -{ - // 配置 Redis - options.RedisConfiguration = "localhost:6379"; - - // 配置 API 文档 - options.EnableSimApiDoc = true; - options.ConfigureSimApiDoc(docOptions => - { - docOptions.ApiGroups = new[] - { - new SimApiDocGroupOption - { - Id = "admin", - Name = "后台管理接口", - Description = "本接口调用需要Scope:sac.api.admin" - }, - new SimApiDocGroupOption - { - Id = "user-v1", - Name = "用户中心接口", - Description = "本接口调用需要Scope:sac.api.user" - } - }; - docOptions.ApiAuth = new SimApiAuthOption - { - Type = new[] { "ClientCredentials", "Implicit", "AuthorizationCode" }, - Scopes = new Dictionary - { - { "sac.api.user", "用户信息接口权限" }, - { "sac.api.admin", "后台管理API" } - }, - AuthorizationUrl = "/connect/authorize", - TokenUrl = "/connect/token" - }; - }); - - // 配置存储服务 - options.EnableSimApiStorage = true; - options.SimApiStorageOptions = Configuration.GetSection("S3").Get(); - - // 配置任务调度 - options.EnableJob = true; - options.ConfigureSimApiJob(jobOptions => - { - jobOptions.DashboardUrl = "/jobs"; - jobOptions.DashboardAuthUser = "admin"; - jobOptions.DashboardAuthPass = "Admin@123!"; - }); - - // 配置事件和 RPC - options.EnableSynapse = true; - - // 其他配置 - options.EnableCors = true; - options.EnableSimApiException = true; - options.EnableSimApiResponseFilter = true; - options.EnableVersionUrl = true; - options.EnableLogger = true; -}); - -// 使用 SimApi -app.UseSimApi(); -``` - -### 6.2 控制器示例 - -```csharp -using Microsoft.AspNetCore.Mvc; -using SimApi.Controllers; -using SimApi.Helpers; - -[ApiController] -[Route("[controller]")] -public class UserController : BaseController -{ - private readonly SimApiStorage _storage; - - public UserController(SimApiStorage storage) - { - _storage = storage; - } - - [HttpGet("{id}")] - public SimApiBaseResponse GetUser(int id) - { - var user = GetUserFromDatabase(id); - ErrorWhenNull(user, 404, "用户不存在"); - return new SimApiBaseResponse(user); - } - - [HttpPost] - [SimApiAuth] // 需要认证 - public SimApiBaseResponse CreateUser(UserCreateDto dto) - { - ErrorWhen(string.IsNullOrEmpty(dto.Name), 400, "用户名不能为空"); - ErrorWhen(dto.Age < 18, 400, "年龄必须大于18岁"); - - var user = CreateUserInDatabase(dto); - return new SimApiBaseResponse(user); - } - - [HttpPost("upload-avatar")] - [SimApiAuth] - public async Task> UploadAvatar(IFormFile file) - { - using var stream = file.OpenReadStream(); - var path = $"/avatars/{LoginInfo.Id}/{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; - _storage.UploadFile(path, stream, file.ContentType); - var url = _storage.GetUrl(path); - return new SimApiBaseResponse(url); - } -} -``` - -### 6.3 任务调度示例 - -```csharp -public class UserService -{ - public void SendWelcomeEmail(string email) - { - // 发送欢迎邮件 - Console.WriteLine($"Sending welcome email to {email}"); - } - - public void CleanupInactiveUsers() - { - // 清理不活跃用户 - Console.WriteLine("Cleaning up inactive users"); - } - - public void GenerateMonthlyReport() - { - // 生成月度报告 - Console.WriteLine("Generating monthly report"); - } -} - -// 配置任务 -public void ConfigureJobs(IServiceProvider serviceProvider) -{ - // 立即发送欢迎邮件 - BackgroundJob.Enqueue(x => x.SendWelcomeEmail("user@example.com")); - - // 每天凌晨清理不活跃用户 - RecurringJob.AddOrUpdate("cleanup-inactive-users", x => x.CleanupInactiveUsers(), Cron.Daily); - - // 每月1日生成月度报告 - RecurringJob.AddOrUpdate("generate-monthly-report", x => x.GenerateMonthlyReport(), "0 0 1 * *"); -} -``` - -## 7. 最佳实践 - -### 7.1 控制器设计 - -- 所有控制器应继承自 `SimApiBaseController` 或其派生类 -- 使用 `Error` 和 `ErrorWhen` 系列方法进行错误处理 -- 对需要认证的接口使用 `[SimApiAuth]` 属性 -- 合理使用 API 分组,便于文档管理 - -### 7.2 存储管理 - -- 为不同类型的文件使用不同的存储路径结构 -- 合理设置文件 URL 的过期时间 -- 对上传的文件进行验证和处理 -- 考虑使用 CDN 加速文件访问 - -### 7.3 任务调度 - -- 合理设置任务的队列和优先级 -- 对长时间运行的任务进行分解 -- 监控任务的执行状态和结果 -- 合理设置任务的重试策略 - -### 7.4 事件和 RPC - -- 为事件和 RPC 方法使用清晰的命名规范 -- 合理设计事件和 RPC 的数据结构 -- 考虑事件处理的幂等性 -- 监控事件和 RPC 的执行情况 - -### 7.5 配置管理 - -- 使用配置文件或环境变量管理配置 -- 对敏感配置进行加密处理 -- 不同环境使用不同的配置 -- 定期审查和更新配置 - -### 7.6 性能优化 - -- 合理使用缓存减少数据库访问 -- 对高频访问的接口进行优化 -- 考虑使用异步方法提高并发性能 -- 监控系统性能并进行调优 - -## 8. 故障排查 - -### 8.1 常见问题 - -#### 认证失败 -- 检查 Token 是否正确 -- 检查 Redis 是否正常运行 -- 检查认证中间件是否正确配置 - -#### 存储服务错误 -- 检查 S3 服务是否正常运行 -- 检查存储配置是否正确 -- 检查网络连接是否正常 - -#### 任务调度错误 -- 检查 Hangfire 仪表盘是否可访问 -- 检查 Redis 是否正常运行 -- 检查任务代码是否有异常 - -#### API 文档生成错误 -- 检查 Swagger 配置是否正确 -- 检查控制器和方法的注释是否完整 -- 检查模型类是否有循环引用 - -### 8.2 日志和监控 - -- 启用 `EnableLogger` 配置查看详细日志 -- 使用应用性能监控工具监控系统状态 -- 定期检查系统日志和错误报告 -- 设置关键指标的告警机制 - -## 9. 版本管理 - -- 访问 `/versions` 查看应用版本和 SimApi 版本 -- 定期更新 SimApi 到最新版本 -- 注意版本升级时的兼容性问题 -- 遵循语义化版本规范管理应用版本 - -## 10. 总结 - -SimApi 是一个功能丰富的 .NET 基础辅助包,提供了一系列实用功能,帮助开发者快速构建和部署 API 服务。通过合理配置和使用 SimApi,可以显著提高开发效率,减少重复代码,提高系统的可维护性和可靠性。 - -本说明书提供了 SimApi 的详细使用方法和最佳实践,希望能帮助开发者更好地使用这个库。如果有任何问题或建议,欢迎反馈和贡献。 \ No newline at end of file