NuGet:
Simcu.SimApi| 目标框架:net8.0/net9.0/net10.0
作者: xRain@SimcuTeam
ASP.NET Core API 基础框架库,提供统一异常拦截、响应封装、Token 认证、Swagger 文档、S3 存储、MQTT 通信、Hangfire 任务调度、Auth Center 网关鉴权与 IAM 权限管理。
varbuilder=WebApplication.CreateBuilder(args);builder.Services.AddSimApi(options =>{// RedisConfiguration 可选:配置则使用 Redis,不配则自动使用 InMemoryoptions.RedisConfiguration="localhost:6379";options.EnableSimApiAuth=true;options.EnableSimApiDoc=true;options.ConfigureSimApiDoc(doc =>{doc.DocumentTitle="我的API";doc.ApiGroups=[new("api","公共接口"),new("admin","管理接口")];});});varapp=builder.Build();app.UseSimApi();app.Run();所有接口输出 JSON,HTTP 状态码始终 200,错误信息在 code 字段:
| code | 含义 |
|---|---|
| 200 | 成功 |
| 204 | 无数据 |
| 400 | 参数错误 |
| 401 | 需要登录 |
| 403 | 无权访问 |
| 404 | 资源不存在 |
| 500 | 服务器错误 |
请求 → SimApiExceptionMiddleware(全异常捕获→HTTP 200+JSON)
→ SimApiAuthMiddleware(Token→LoginInfo)
→ [SimApiSign] Filter(签名)
→ [SimApiAuth] Filter(登录检查)
→ OnActionExecuting(模型验证→code 400)
→ Action
→ SimApiResponseFilter(封装响应)
SimApi/
├── Attributes/ # [SimApiAuth] [SimApiDoc] [SimApiSign] [AesBody] [SynapseEvent] [SynapseRpc] [OriginResponse]
├── AuthSDK/ # Auth Center & IAM SDK
│ ├── SimApiAuthClient.cs HTTP 客户端(签名调用 Auth Center)
│ ├── SimApiAuthCenter.cs Auth Center API 封装
│ ├── SimApiAuthCenterDto.cs 数据模型
│ ├── SimApiAuthCenterMiddleware.cs 网关鉴权中间件
│ ├── SimApiAuthIam.cs IAM 权限管理 API
│ └── SimApiAuthIamDto.cs IAM 数据模型
├── Communications/ # SimApiBaseResponse, PageResponse<T>, SimApiLoginItem, SimApiBaseRequest
├── Configurations/ # SimApiOptions + 各模块 Option 类
├── Controllers/ # SimApiBaseController, SimApiCommonController, SimApiAuthController
├── Helpers/ # SimApiError, SimApiAuth, SimApiCache, SimApiHttpClient, SimApiStorage, SimApiUtil, SimApiAesUtil
├── Interfaces/ # ISimApiAuthChecker
├── Middlewares/ # SimApiExceptionMiddleware, SimApiAuthMiddleware
├── Synapse/ # MQTT Pub/Sub + RPC + Config Store
├── Exceptions/ # SimApiException
├── Models/ # SimApiBaseModel
├── SwaggerFilters/ # 文档自动过滤器
├── ModelBinders/ # AesBody, SimApiSign
├── Logger/ # 彩色控制台日志
└── SimApiExtensions.cs # AddSimApi + UseSimApi
独立静态类 SimApi.Helpers.SimApiError。所有错误最终 → throw new SimApiException(code, message) → 中间件捕获。
使用: 文件顶部加 using static SimApi.Helpers.SimApiError;
// 直接抛错voidError(intcode=500,stringmessage="");// condition 为 true 时抛错 (code 默认 400)voidErrorWhen([DoesNotReturnIf(true)]boolcondition,intcode=400,stringmessage="");// 同上(别名)voidErrorWhenTrue([DoesNotReturnIf(true)]boolcondition,intcode=400,stringmessage="");// condition 为 false 时抛错voidErrorWhenFalse([DoesNotReturnIf(false)]boolcondition,intcode=400,stringmessage="");// obj 为 null 时抛错 (code 默认 404)voidErrorWhenNull([NotNull]object?condition,intcode=404,stringmessage="");usingstaticSimApi.Helpers.SimApiError;varuser=db.Users.Find(id);ErrorWhenNull(user,404,"用户不存在");ErrorWhen(amount<=0,400,"金额无效");ErrorWhenFalse(hasPermission,403,"无权操作");Error(500,"服务器内部错误");publicclassSimApiBaseController:Controller{// 当前登录信息(需 EnableSimApiAuth)protectedSimApiLoginItemLoginInfo=>(SimApiLoginItem)HttpContext.Items["LoginInfo"]!;protectedstringLoginToken=>(string)HttpContext.Items["LoginToken"]!;// OnActionExecuting 自动验证 ModelState → 无效时抛 code 400}内建: [Consumes("application/json")] + [Produces("application/json")]
| 路由 | 方法 | 条件 | 说明 |
|---|---|---|---|
/versions | GET/POST | VersionRoute != null(默认) | 返回 SimApi/App 版本 |
/user/info | POST | EnableSimApiAuth + UserInfoRoute != null | 需登录,返回 LoginInfo |
/auth/logout | POST | EnableSimApiAuth + LogoutRoute != null | 退出登录(可自定义路由) |
/swagger | GET | EnableSimApiDoc | Swagger UI |
/jobs | GET | EnableJob + DashboardUrl != null | Hangfire 控制台 |
/exception/{code:int} | GET | 始终 | 错误反馈页面 |
| 场景 | 返回类型 |
|---|---|
| 写操作 | void |
| 单条查询 | 直接 Entity |
| 列表查询 | Entity[] |
| 分页 | PageResponse<Entity[]> |
| 自定义状态 | SimApiBaseResponse |
| 跳封装 | 方法加 [OriginResponse] |
stringLogin(SimApiLoginItemloginItem,TimeSpan?expireTime=null,string?token=null);// 默认7天stringUpdate(SimApiLoginItemloginItem,stringtoken);SimApiLoginItem?GetLogin(stringtoken);SimApiLoginItem[]GetAllLogins(stringuserId);voidLogout(stringtoken);voidLogoutAll(stringuserId);// LoginItem 结构publicclassSimApiLoginItem{requiredstringId;string[]Type=["user"];Dictionary<string,string>Meta=[];Dictionary<string,object?>Extra=[];}Token 传参: Header Token: <value>
SimApiAuth 内部自动判断,无需手动配置:
| 条件 | 存储后端 | 用户↔Token 映射 |
|---|---|---|
配置了 RedisConfiguration | Redis(IDistributedCache + Set) | Redis Set |
未配置 RedisConfiguration | InMemory(DistributedMemoryCache) | ConcurrentDictionary |
- Redis 模式:支持多实例共享,Token 持久化,适合生产环境
- InMemory 模式:零配置即可启用
EnableSimApiAuth,适合开发/测试/单实例场景。注意重启后所有登录态丢失
// 不配 Redis 也能用 Authbuilder.Services.AddSimApi(options =>{options.EnableSimApiAuth=true;// 自动使用 InMemory});publicinterfaceISimApiAuthChecker{voidRun(SimApiLoginItemloginItem,stringtoken);}// 实现后自动注册为 Scoped,每次认证后调用启用: EnableSimApiAuthGate = true
options.EnableSimApiAuthGate=true;options.ConfigureSimApiAuthCenter(gate =>{gate.Server="https://auth.coce.cc";gate.AppId="your-app-id";gate.AppKey="your-app-key";gate.UseMiddleware=true;// 内部应用启用,解析 X-SimApi-Gate-Auth Headergate.UseIam=true;// 启用 IAM});继承 SimApiHttpClient,自动配置 Server/AppId/AppKey,对 Auth Center 发起签名请求。
// === 签名验证 ===voidVerifySign(stringappId,stringtimestamp,stringnonce,stringsign);// === 群组 ===GroupRelatedItem[]?GroupRelated(stringprofileId);AppAndProfileItem[]?GroupSearch(stringkeyword,intskip=0,inttake=20);GroupDetailTreeNode?GroupDetail(stringgroupId,stringprofileId);string[]?GroupRelatedIndex(stringgroupId,stringprofileId);// === Profile ===AppAndProfileItem[]?ProfileSearch(stringkeyword,intskip=0,inttake=20);AppAndProfileItem[]?ProfileList(string[]ids);// === 内部应用专用 ===boolCheckIsAppOwner(stringprofileId,stringapplicationId);AppAndProfileItem[]?GetAppList(stringprofileId,IEnumerable<string>appIds);// === 登录 ===GetCodeResponseGetLoginCode(string?scene=null,Dictionary<string,object>?data=null,string?backUrl=null);LoginInfoResponseGetLoginInfo(stringcode,string?scene=null);// === 安全验证(二次确认) ===GetCodeResponseGetConfirmCode(stringscene,stringuserId,Dictionary<string,object>?data=null,string?backUrl=null);ConfirmResponseConfirm(stringcode,stringscene,string?userId=null);GetCodeResponse: { Code, Server, FullUrl }
voidRegisterPermissions(PermissionItem[]permissions);string[]GetPermissionOwned(stringprofileId);voidCheckPermission(stringprofileId,stringpermission);PermissionItem: { Identifier, Name, Group, Description }
内部应用专用。解析请求头 X-SimApi-Gate-Auth / X-SimApi-Gate-Time / X-SimApi-Gate-Sign,MD5 验签后将 Base64 解码的用户信息注入 HttpContext.Items["LoginInfo"]。
[SimApiAuth]// 仅检查登录[SimApiAuth("admin")]// 单角色[SimApiAuth("admin,manager")]// 逗号分隔 OR 关系[SimApiAuth(new[]{"a","b"})]// 数组形式- 加在 Controller 类 或 方法 上
- 禁止 代码中手动判断
LoginInfo.Type.Contains(...)做权限控制
[SimApiDoc("分组名","接口名称")][SimApiDoc("分组名","接口名称","详细描述")][SimApiDoc(new[]{"tag1","tag2"},"接口名称")][SimApiSign(KeyProvider=typeof(MySignProvider))]// 签名: MD5(field1=v1&...&appId=xxx×tamp=ts&nonce=nnn&密钥)实现 SimApiSignProviderBase:
publicclassMySignProvider:SimApiSignProviderBase{// 可覆盖: AppIdName, TimestampName, NonceName, SignName, QueryExpires(秒), DuplicateRequestProtection, SignFieldspublicoverridestring?GetKey(string?appId){ ...}}[AesBody(KeyProvider=typeof(MyAesKeyProvider))]MyRequest req
// 客户端提交: {"data": "Base64(AES-256-CBC 密文)"}实现 AesBodyProviderBase,覆盖 AppIdName 和 GetKey(appId)。
[SynapseEvent("order/created")]// 指定 eventName[SynapseEvent]// 不指定 = 方法名// 参数: 1个(string eventName) / 2个(string eventName, T data)// 注意: 至少需要1个参数[SynapseRpc]// 注册名 "ClassName.MethodName"[SynapseRpc("customName")]// 自定义名// 参数: 0~2个,第2个固定 Dictionary<string,string>(headers)[HttpGet][OriginResponse]publicstringGetRaw()=>"raw";options.ConfigureSimApiDoc(doc =>{doc.DocumentTitle="接口文档";doc.ApiGroups=[new("api","公共"),new("admin","管理","描述可选")];doc.SupportedMethod=[SubmitMethod.Post];// 默认仅POSTdoc.ApiAuth=newSimApiAuthOption{Type=["SimApiAuth"]};// 认证方式});每组通过 [ApiExplorerSettings(GroupName = "admin")] 分类。
| 过滤器 | 效果 |
|---|---|
SimApiResponseOperationFilter | 返回值包装为 SimApiBaseResponse<T> |
SimApiAuthOperationFilter | 鉴权接口 + Token Header |
SimApiSignOperationFilter | 签名接口注入签名参数 |
AesBodyOperationFilter | AES 接口展示原始结构 |
GlobalDynamicObjectSchemaFilter | object/Dictionary → Schema |
RemoveEmptyTagsFilter | 清除空分组 |
基于 MinIO SDK(S3 兼容)。
options.EnableSimApiStorage=true;options.ConfigureSimApiStorage(s =>{s.Endpoint="http://minio:9000";// 不能以 / 结尾s.AccessKey="admin";s.SecretKey="pass";s.Bucket="bucket";s.ServeUrl="http://cdn.example.com/bucket";// 不能以 / 结尾});GetUploadUrlResponseGetUploadUrl(stringpath,intexpire=7200);// 返回: { UploadUrl, DownloadUrl, Path }stringGetDownloadUrl(stringpath,intexpire=600);voidUploadFile(stringpath,Streamstream,stringcontentType="image/png");string?FullUrl(string?path);// 路径→完整URLstring?GetUrl(string?path);// 同上string?GetPath(string?url);// URL→相对路径IMinioClientClient{get;}// 底层 MinIO 客户端路径必须以
/开头
通过 EnableSimApiCache(默认 true)控制。Key 自动加前缀 SimApi:Cache:。
存储后端与 SimApiAuth 一致:配了 RedisConfiguration 就用 Redis,否则用 InMemory。
voidSet(stringkey,objectvalue,DistributedCacheEntryOptions?options=null);T?Get<T>(stringkey);string?Get(stringkey);boolHasKey(stringkey);voidRemove(stringkey);用于调用其他带签名/AES 的 SimApi 服务。
virtualstringServer{get;init;}virtualstringAppId{get;init;}virtualstringAppKey{get;init;}virtualstringSignName{get;init;}="sign";virtualstringTimestampName{get;init;}="timestamp";virtualstringNonceName{get;init;}="nonce";virtualstring?AppIdName{get;init;}="appId";virtualstring[]SignFields{get;init;}=[];TSignQuery<T>(stringurl,object?body=null,Dictionary<string,string>?queries=null);TAesQuery<T>(stringurl,objectbody);TAesSignQuery<T>(stringurl,objectbody,Dictionary<string,string>?queries=null);Hangfire + Redis。
options.EnableJob=true;options.ConfigureSimApiJob(job =>{job.DashboardUrl="/jobs";// null = 不开启job.DashboardAuthUser="admin";job.DashboardAuthPass="pass";job.Database=1;// Redis DB 编号job.Servers=[new(){Queues=["default"],WorkerNum=5},new(){Queues=["email"],WorkerNum=2}];});BackgroundJob.Enqueue(()=>DoWork());BackgroundJob.Schedule(()=>DoWork(),TimeSpan.FromMinutes(5));RecurringJob.AddOrUpdate("id",()=>DoWork(),Cron.Daily);BackgroundJob.ContinueJobWith(id,()=>Step2());基于 MQTTnet v5,WebSocket 连接。
options.EnableSynapse=true;options.ConfigureSimApiSynapse(s =>{s.Websocket="ws://mqtt:8083/mqtt";s.Username="user";s.Password="pass";s.SysName="my-system";s.AppName="order-service";s.AppId="instance-001";// 不填自动GUIDs.RpcTimeout=3;// 秒s.EventLoadBalancing=false;// $queue 负载均衡s.EnableConfigStore=true;// 分布式配置中心});| 用途 | Topic 格式 |
|---|---|
| 事件发布 | {SysName}/event/{AppName}/{eventName} |
| 事件订阅 | {SysName}/event/{eventName} (或 $queue/ 前缀) |
| RPC 请求 | {SysName}/{targetApp}/rpc/server/{method} |
| RPC 响应 | {SysName}/{callerApp}/rpc/client/{AppId}/{messageId} |
| 配置 | {SysName}/synapse-config-store/{key} (Retain) |
// 事件boolEvent(stringeventName,dynamic?param=null);// RPC (同步)SimApiBaseResponse<T>Rpc<T>(stringappName,stringmethod,dynamic?param=null,Dictionary<string,string>?headers=null,int?timeout=null);// RPC 内报错voidRpcError(intcode,stringmessage="");voidRpcErrorWhen(boolcondition,intcode,stringmessage="");// 分布式配置boolSetConfig(stringkey,stringvalue);string?GetConfig(stringkey);带 [SynapseRpc] / [SynapseEvent] 的类自动扫描注册为 Scoped 服务。
AES-256-CBC + PKCS7,密钥经 SHA256 处理。
stringcipher=SimApiAesUtil.Encrypt("明文","任意长度密钥");stringplain=SimApiAesUtil.Decrypt(cipher,"任意长度密钥");DateTimeCstNow;// UTC+8doubleTimestampNow;// 秒级 UnixstringSimApiVersion;// NuGet 包版本stringAppVersion;// 宿主应用版本stringMd5(stringsrc,stringmode="x2");// x2=32位, x3=48位, x4=64位stringSha1(stringsrc,stringmode="x2");stringBase64Encode(stringstr);stringBase64Decode(stringbase64Str);stringBase64Encode(objectobj);T?Base64Decode<T>(stringbase64Str);stringJson(object?obj);// camelCase,中文不转义T?FromJson<T>(stringjson);TXmlDeserialize<T>(stringxml);JsonSerializerOptionsJsonOption;// 可复用boolCheckCell(stringcell);// 手机号验证boolCheckEmail(stringemail);IQueryable<T>Paginate<T>(thisIQueryable<T>query,intpage,intcount);publicclassUserEntity:SimApiBaseModel{// 自动: Id(GUID string), CreatedAt, UpdatedAt// 默认忽略映射: Id, CreatedAt, UpdatedAt}voidMapData<TS>(TSsource,boolmapAll=false);// 同名同类型非null属性voidMapData<TS>(TSsource,string[]mapFields);// 指定字段voidUpdateTime();| 类型 | 命名 | 示例 |
|---|---|---|
| 请求 | [动作]Request | UserEditRequest |
| 响应 | [动作]Response | TokenResponse |
| 载体 | [含义]Data/Item | GenerateData |
classSimApiStringIdOnlyRequest{requiredstringId;}classSimApiOneFieldRequest<T>{T?Data;}classSimApiBasePageRequest{intPage=1;intCount=20;}classSimApiBaseResponse{intCode;stringMessage;}// code=200 默认classSimApiBaseResponse<T>:SimApiBaseResponse{T?Data;}classPageResponse<T>{T?List;intPage;intCount;intTotal;}builder.Services.AddSimApi(options =>{options.RedisConfiguration="localhost:6379";// 功能开关options.EnableSimApiAuth=false;// Token 认证options.EnableSimApiCache=true;// 缓存(Redis 或 InMemory)options.EnableSimApiAuthGate=false;// Auth Center 网关鉴权options.EnableSimApiDoc=false;// Swagger 文档options.EnableSimApiStorage=false;// S3 存储options.EnableJob=false;// Hangfireoptions.EnableSynapse=false;// MQTToptions.EnableSimApiHttpClient=false;// 外部 HTTP 调用options.EnableLogger=true;// 控制台日志options.EnableCors=true;// 全量 CORSoptions.EnableSimApiException=true;// 全局异常拦截options.EnableSimApiResponseFilter=true;// 响应统一封装options.EnableForwardHeaders=true;// 反向代理 Headeroptions.EnableLowerUrl=true;// URL 小写// 子模块配置options.ConfigureSimApiDoc(doc =>{ ...});options.ConfigureSimApiStorage(s =>{ ...});options.ConfigureSimApiJob(job =>{ ...});options.ConfigureSimApiSynapse(s =>{ ...});options.ConfigureSimApiAuthCenter(gate =>{ ...});options.ConfigureSimApiHttpClient(http =>{ ...});options.ConfigureSimApiRoute(route =>{ ...});options.ConfigureSimApiException(ex =>{ ...});});| ❌ 错误 | ✅ 正确 |
|---|---|
存储路径 avatars/file.jpg(无前导 /) | 必须以 / 开头 |
s.Endpoint = "http://x:9000/" | 不能以 / 结尾 |
synapse.PublishEvent(...) | 方法名是 synapse.Event(...) |
synapse.CallRpcAsync(...) | 方法名是 synapse.Rpc<T>(...) |
| HTTP 4xx/5xx 状态码 | 永远 HTTP 200,错误在 JSON code |
SupportedMethod 写多种方法 | 默认仅 POST |
SimApiStorageOptions = Configuration.GetSection(...) | 用 ConfigureSimApiStorage(s => {...}) |
代码中 LoginInfo.Type.Contains("admin") | 用 [SimApiAuth("admin")] |
return ActionResult<T> | 直接返回 Entity / void |
| ❌ 禁止 | ✅ 正确 |
|---|---|
| HTTP 4xx/5xx 表达业务错误 | HTTP 200 + JSON code |
throw new Exception(msg) | ErrorWhen 或 throw new SimApiException(code, msg) |
| 鉴权 Attribute 只放方法 | 可以放 Controller 类上 |
手动判断 LoginInfo.Type.Contains(...) | [SimApiAuth("role")] |
| Entity 配导航属性 / Fluent API | Convention 自动映射 |
| 花括号块命名空间 | 文件范围 namespace X; |
| 传统构造函数注入 | 主构造函数 |
new List<T>() / new string[]{} | [] 集合表达式 |
Count() > 0 | Any() |
ToList() → 数组 | 直接 ToArray() |
| 全局 catch 吞异常 | 让异常冒泡到 SimApiExceptionMiddleware |