From bd674dcc313875f750d560795829f16741e35193 Mon Sep 17 00:00:00 2001 From: xRain Date: Mon, 19 Aug 2024 03:39:35 +0800 Subject: [PATCH] add cocesdk --- CoceSdk/CoceApp.cs | 235 ++++++++++++++++++++++++++ CoceSdk/CoceAppSdkOption.cs | 15 ++ CoceSdk/CoceModels.cs | 43 +++++ Communications/SimApiLoginItem.cs | 8 +- Configurations/SimApiOptions.cs | 13 +- Controllers/SimApiCoceController.cs | 48 ++++++ Controllers/SimApiCommonController.cs | 12 +- Helpers/SimApiAuth.cs | 44 +---- SimApi.csproj | 5 +- SimApiExtensions.cs | 37 +++- 10 files changed, 406 insertions(+), 54 deletions(-) create mode 100644 CoceSdk/CoceApp.cs create mode 100644 CoceSdk/CoceAppSdkOption.cs create mode 100644 CoceSdk/CoceModels.cs create mode 100644 Controllers/SimApiCoceController.cs diff --git a/CoceSdk/CoceApp.cs b/CoceSdk/CoceApp.cs new file mode 100644 index 0000000..b75a087 --- /dev/null +++ b/CoceSdk/CoceApp.cs @@ -0,0 +1,235 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text; +using System.Text.Json; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; +using SimApi.Communications; +using SimApi.Configurations; +using SimApi.Helpers; + +namespace SimApi.CoceSdk; + +public class CoceApp(SimApiOptions simApiOptions, ILogger logger, IDistributedCache cache) +{ + + /// + /// 获取Level Token + /// + /// + /// + /// + public LevelTokenResponse? GetLevelToken(string lv1Token, int level = 5) + { + var dict = new Dictionary + { + { "lv1Token", lv1Token }, + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + { "level", level } + }; + return QueryAppApi("/api/app/token", dict); + } + + /// + /// 通过用户手机号搜索用户 + /// + /// + /// + public UserInfo? SearchUserByPhone(string phone) + { + var dict = new Dictionary + { + { "cell", phone }, + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + }; + return QueryAppApi("/api/app/user/search-by-phone", dict); + } + + /// + /// 通过给出的UserId列表获取用户信息 + /// + /// + /// + public UserInfo[]? SearchUserByIds(IEnumerable userIds) + { + var dict = new Dictionary + { + { "ids", string.Join(",", userIds) }, + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + }; + return QueryAppApi("/api/app/user/search-by-ids", dict); + } + + /// + /// 向用户发送消息 + /// + /// + /// + /// + /// + public bool SendUserMessage(string userId, string title, string content) + { + var dict = new Dictionary + { + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + { "userId", userId }, + { "type", "text" }, + { "title", title }, + { "text", content } + }; + return QueryAppApiNoResp("/api/app/message", dict); + } + + /// + /// 创建交易订单号 + /// + /// + /// + /// + /// + public string? TradeCreate(string name, int amount, string ext) + { + var dict = new Dictionary + { + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + { "amount", amount }, + { "ext", ext }, + { "name", name } + }; + return QueryAppApi("/api/app/trade/create", dict); + } + + /// + /// 查询订单状态 + /// + /// + /// + public CheckTradeResponse? TradeCheck(string tradeNo) + { + var dict = new Dictionary + { + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + { "tradeNo", tradeNo } + }; + return QueryAppApi("/api/app/trade/result", dict); + } + + /// + /// 对订单进行退款 + /// + /// + /// + public bool TradeRefund(string tradeNo) + { + var dict = new Dictionary + { + { "time", (int)SimApiUtil.TimestampNow }, + { "appId", simApiOptions.CoceSdkOptions.AppId! }, + { "tradeNo", tradeNo } + }; + return QueryAppApiNoResp("/api/app/trade/refund", dict); + } + + + private T? QueryAppApi(string endpoint, Dictionary request) + { + var response = QueryAppApi(endpoint, request); + var result = response.Content.ReadFromJsonAsync>().Result!; + if (result.Code == 200) return result.Data; + logger.LogDebug("发生错误: {Code} => {Message}", result.Code, result.Message); + return default; + } + + private bool QueryAppApiNoResp(string endpoint, Dictionary request) + { + var response = QueryAppApi(endpoint, request); + var result = response.Content.ReadFromJsonAsync().Result!; + return result.Code == 200; + } + + private HttpResponseMessage QueryAppApi(string endpoint, Dictionary request) + { + var platUrl = simApiOptions.CoceSdkOptions.ApiEndpoint + endpoint; + var sorted = request.OrderBy(x => x.Key); + var signStr = sorted.Aggregate("", (current, item) => current + $"{item.Key}={item.Value}&").TrimEnd('&'); + logger.LogDebug("签名的字符串: {SignStr}", signStr); + var sign = SimApiUtil.Md5(signStr + simApiOptions.CoceSdkOptions.AppKey); + logger.LogDebug("签名: {Sign}", sign); + request.Add("sign", sign); + logger.LogDebug("请求地址: {PlatUrl} => {Data}", platUrl, JsonSerializer.Serialize(request)); + var http = new HttpClient(); + return http.PostAsJsonAsync(platUrl, request).Result; + } + + + /// + /// 获取用户的群组信息 + /// + /// Level >=2 的Token + /// + public IEnumerable? GetUserGroups(string token) + { + const string uri = "/api/lv2/user/groups"; + var resp = ProxyQuery(uri, token,"{}"); + return resp; + } + + /// + /// 获取用户信息 + /// + /// + /// + public UserInfo? GetUserInfo(string token) + { + const string uri = "/api/lv1/user/info"; + return ProxyQuery(uri, token); + } + + public void SaveToken(string userId, string levelToken) + { + cache.SetString($"LEVEL:TOKEN:{userId}", levelToken); + } + + public string? GetToken(string userId) + { + return cache.GetString($"LEVEL:TOKEN:{userId}"); + } + + + public dynamic? ProxyQuery(string uri, string token, string json) => ProxyQuery(uri, token, json); + + public dynamic? ProxyQueue(string uri, string token, object data) => + ProxyQuery(uri, token, JsonSerializer.Serialize(data)); + + public T? ProxyQueue(string uri, string token, object data) => + ProxyQuery(uri, token, JsonSerializer.Serialize(data)); + + public T? ProxyQuery(string uri, string token, string json = "{}") + { + var http = new HttpClient(); + http.DefaultRequestHeaders.Add("Token", token); + var realUrl = simApiOptions.CoceSdkOptions.ApiEndpoint + uri; + var response = http.PostAsync(realUrl, new StringContent(json, Encoding.UTF8, "application/json")) + .Result; + var resp = response.Content.ReadFromJsonAsync>().Result!; + if (resp.Code != 200) + { + logger.LogDebug("请求发生错误: {RespCode} => {RespMessage}", resp.Code, resp.Message); + } + + return resp.Code == 200 ? resp.Data : default; + } + + public ConfigResponse GetConfig() + { + return new ConfigResponse(simApiOptions.CoceSdkOptions.AppId!, simApiOptions.CoceSdkOptions.AuthEndpoint); + } +} \ No newline at end of file diff --git a/CoceSdk/CoceAppSdkOption.cs b/CoceSdk/CoceAppSdkOption.cs new file mode 100644 index 0000000..910c99b --- /dev/null +++ b/CoceSdk/CoceAppSdkOption.cs @@ -0,0 +1,15 @@ +namespace SimApi.CoceSdk; + +public class CoceAppSdkOption +{ + /** + * 中心API服务器地址 + */ + public string ApiEndpoint { get; set; } = "https://api.coce.cc"; + + public string AuthEndpoint { get; set; } = "https://home.coce.cc"; + + public string? AppId { get; set; } + + public string? AppKey { get; set; } +} \ No newline at end of file diff --git a/CoceSdk/CoceModels.cs b/CoceSdk/CoceModels.cs new file mode 100644 index 0000000..545ca3a --- /dev/null +++ b/CoceSdk/CoceModels.cs @@ -0,0 +1,43 @@ +using System; + +namespace SimApi.CoceSdk; + +public record ConfigResponse(string AppId, string AuthUrl); + +public record LevelTokenResponse(string Token, string UserId, int TokenLevel); + +public record GroupInfo(string Id, string Name, string Image, string Description, string Role); + +public record UserInfo(string UserId, string Name, string Image); + +public record CheckTradeResponse( + string TradeNo, + int Amount, + int Fee, + string Name, + string? Ext, + string Status, + DateTime CreatedAt, + DateTime? FinishedAt, + DateTime? RefundAt, + DateTime? CloseAt); + +public class UserInfoWithGroup +{ + public string? UserId { get; set; } + + public string? Name { get; set; } + + public string? LevelToken { get; set; } + + public UserGroupItem[]? UserGroupItems { get; set; } +} + +public class UserGroupItem +{ + public string? GroupId { get; set; } + + public string? GroupName { get; set; } + + public string? GroupRole { get; set; } +} \ No newline at end of file diff --git a/Communications/SimApiLoginItem.cs b/Communications/SimApiLoginItem.cs index 3ad8fea..5693636 100644 --- a/Communications/SimApiLoginItem.cs +++ b/Communications/SimApiLoginItem.cs @@ -5,4 +5,10 @@ namespace SimApi.Communications; /// /// 登录信息中间件 /// -public record SimApiLoginItem(string Id, string[] Type,Dictionary? Meta = null); \ No newline at end of file +public class SimApiLoginItem +{ + public string? Id { get; set; } + public string[] Type { get; set; } = new[] { "user" }; + public Dictionary? Meta { get; set; } = null; + public object? Extra { get; set; } = null; +}; \ No newline at end of file diff --git a/Configurations/SimApiOptions.cs b/Configurations/SimApiOptions.cs index 6141870..6f08cd5 100644 --- a/Configurations/SimApiOptions.cs +++ b/Configurations/SimApiOptions.cs @@ -1,4 +1,5 @@ using System; +using SimApi.CoceSdk; namespace SimApi.Configurations; @@ -16,6 +17,14 @@ public class SimApiOptions /// public bool EnableSimApiAuth { get; set; } + + /// + /// 是否使用CoceSdk + /// + public bool EnableCoceSdk { get; set; } + + public CoceAppSdkOption CoceSdkOptions { get; set; } = new(); + /// /// 启用在线文档,启用后 访问 /swagger 可以查看对应的api文档。 /// default: false @@ -75,9 +84,9 @@ public class SimApiOptions options?.Invoke(SimApiSynapseOptions); } - public void ConfigureSimApiSynapse(SimApiSynapseOptions options) + public void ConfigureCoceSdk(Action? options = null) { - SimApiSynapseOptions = options; + options?.Invoke(CoceSdkOptions); } public void ConfigureSimApiDoc(Action? options = null) diff --git a/Controllers/SimApiCoceController.cs b/Controllers/SimApiCoceController.cs new file mode 100644 index 0000000..eb8ed6a --- /dev/null +++ b/Controllers/SimApiCoceController.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using SimApi.Attributes; +using SimApi.CoceSdk; +using SimApi.Communications; +using SimApi.Helpers; + +namespace SimApi.Controllers; + +public class SimApiCoceController(CoceApp coce,SimApiAuth auth) : SimApiBaseController +{ + [HttpPost] + public SimApiBaseResponse GetConfig() + { + return new SimApiBaseResponse(coce.GetConfig()); + } + + [HttpPost] + public SimApiBaseResponse Login([FromBody] SimApiOneFieldRequest request) + { + var data = coce.GetLevelToken(request.Data!); + ErrorWhenNull(data, 400); + coce.SaveToken(data!.UserId, data.Token); + var userinfo = coce.GetUserInfo(data!.Token); + var meta = new Dictionary + { + { "name", userinfo!.Name }, + { "image", userinfo.Image } + }; + var groups = coce.GetUserGroups(data.Token!)!; + var loginItem = new SimApiLoginItem + { + Id = data.UserId, + Meta = meta, + Extra = groups + }; + return new SimApiBaseResponse(auth.Login(loginItem)); + } + + [HttpPost, SimApiAuth] + public SimApiBaseResponse ListGroups() + { + var levelToken = coce.GetToken(LoginInfo.Id!); + var groups = coce.GetUserGroups(levelToken!)!; + return new SimApiBaseResponse(groups.ToArray()); + } +} \ No newline at end of file diff --git a/Controllers/SimApiCommonController.cs b/Controllers/SimApiCommonController.cs index adfa6fe..2dfbb4b 100644 --- a/Controllers/SimApiCommonController.cs +++ b/Controllers/SimApiCommonController.cs @@ -4,8 +4,6 @@ using SimApi.Communications; using SimApi.Helpers; namespace SimApi.Controllers; - -[ApiExplorerSettings(GroupName = "api")] public class SimApiCommonController(SimApiAuth auth) : SimApiBaseController { /// @@ -24,10 +22,10 @@ public class SimApiCommonController(SimApiAuth auth) : SimApiBaseController /// 检测用户登陆的控制器 /// /// - [HttpPost("/auth/check"), SimApiDoc("认证", "检测登陆")] + [HttpPost, SimApiDoc("认证", "检测登陆")] public SimApiBaseResponse CheckLogin() { - ErrorWhenNull(LoginInfo, 401); + ErrorWhenNull(LoginInfo, 401,"未登录"); return new SimApiBaseResponse { Data = LoginInfo.Id @@ -38,7 +36,7 @@ public class SimApiCommonController(SimApiAuth auth) : SimApiBaseController /// 退出登陆 /// /// - [HttpPost("/auth/logout"), SimApiDoc("认证", "退出登陆")] + [HttpPost, SimApiDoc("认证", "退出登陆")] public SimApiBaseResponse Logout() { string? token = null; @@ -51,8 +49,8 @@ public class SimApiCommonController(SimApiAuth auth) : SimApiBaseController auth.Logout(token!); return new SimApiBaseResponse(); } - - [HttpPost("/logined"),SimApiAuth] + + [HttpPost,SimApiAuth] public SimApiBaseResponse UserInfo() { return new SimApiBaseResponse(LoginInfo); diff --git a/Helpers/SimApiAuth.cs b/Helpers/SimApiAuth.cs index fc546a3..bf3ba5f 100644 --- a/Helpers/SimApiAuth.cs +++ b/Helpers/SimApiAuth.cs @@ -13,48 +13,16 @@ namespace SimApi.Helpers; public class SimApiAuth(IDistributedCache cache) { /// - /// 产生一个Token记录并返回Token + /// 登录信息 /// - /// - /// - /// + /// /// /// - public string Login(string id, Dictionary? meta = null, string type = "user", string? token = null) + public string Login(SimApiLoginItem loginItem, string? token = null) { - return Login(id, meta, new[] { type }, token); - } - - /// - /// 产生一个Token并记录用户ID角色[多角色] - /// - /// - /// - /// - /// - /// - // ReSharper disable once MemberCanBePrivate.Global - public string Login(string id, Dictionary? meta, string[] type, string? uuid = null) - { - uuid ??= Guid.NewGuid().ToString(); - var loginItem = new SimApiLoginItem(id, type, meta); - cache.SetString(uuid, JsonSerializer.Serialize(loginItem)); - return uuid; - } - - /// - /// 设置登录的Meta信息 - /// - /// - /// - /// - public bool SetMeta(string token, Dictionary meta) - { - var login = GetLogin(token); - if (login == null) { return false; } - var newLogin = new SimApiLoginItem(login.Id, login.Type, login.Meta); - cache.SetString(token,JsonSerializer.Serialize(newLogin)); - return true; + token ??= Guid.NewGuid().ToString(); + cache.SetString(token, JsonSerializer.Serialize(loginItem)); + return token; } /// diff --git a/SimApi.csproj b/SimApi.csproj index f3700f4..5a2b317 100644 --- a/SimApi.csproj +++ b/SimApi.csproj @@ -21,15 +21,14 @@ - - - + + diff --git a/SimApiExtensions.cs b/SimApiExtensions.cs index 6e04b25..924acb7 100644 --- a/SimApiExtensions.cs +++ b/SimApiExtensions.cs @@ -7,6 +7,7 @@ using SimApi.Middlewares; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using SimApi.CoceSdk; using SimApi.Configurations; using SimApi.Logger; @@ -38,6 +39,11 @@ public static class SimApiExtensions builder.AddSingleton(); } + if (simApiOptions.EnableCoceSdk) + { + builder.AddSingleton(); + } + if (simApiOptions.EnableCors) { builder.AddCors(cors => cors.AddPolicy("any", @@ -147,7 +153,7 @@ public static class SimApiExtensions Id = "oauth2" } }, - new[] { "SimApiAuth" } + ["SimApiAuth"] } }); } @@ -192,12 +198,12 @@ public static class SimApiExtensions public static IHost UseSimApi(this IHost builder) { - var options = builder.Services.GetRequiredService(); + var options = builder.Services.GetRequiredService(); var logger = builder.Services.GetRequiredService>(); logger.LogInformation("当前时区: {LocalId}", TimeZoneInfo.Local.Id); - + //请求一下检测存储错误 if (options.EnableSimApiStorage) { @@ -205,6 +211,13 @@ public static class SimApiExtensions builder.Services.GetService(); } + if (options.EnableCoceSdk) + { + logger.LogInformation("开始配置CoceAppSdk...\nApi入口: {ApiUrl}\nAuth入口:{AuthUrl}n\nAppId: {AppId}", + options.CoceSdkOptions.ApiEndpoint, options.CoceSdkOptions.AuthEndpoint, + options.CoceSdkOptions.AppId); + } + if (options.EnableLowerUrl) { logger.LogInformation("开始配置使用URL小写..."); @@ -247,6 +260,24 @@ public static class SimApiExtensions { logger.LogInformation("开始配置SimApiAuth..."); builder.UseMiddleware(); + builder.MapControllerRoute(name: "GetUserInfo", pattern: "/user/info", + defaults: new { controller = "SimApiCommon", action = "UserInfo" }); + builder.MapControllerRoute(name: "CheckLogin", pattern: "/auth/check", + defaults: new { controller = "SimApiCommon", action = "CheckLogin" }); + builder.MapControllerRoute(name: "Logout", pattern: "/auth/logout", + defaults: new { controller = "SimApiCommon", action = "Logout" }); + if (options.EnableCoceSdk) + { + logger.LogInformation("开始配置CoceAppSdk...\nApi入口: {ApiUrl}\nAuth入口:{AuthUrl}n\nAppId: {AppId}", + options.CoceSdkOptions.ApiEndpoint, options.CoceSdkOptions.AuthEndpoint, + options.CoceSdkOptions.AppId); + builder.MapControllerRoute(name: "LoginUseCoce", pattern: "/auth/login", + defaults: new { controller = "SimApiCoce", action = "Login" }); + builder.MapControllerRoute(name: "LoginUseCoce", pattern: "/user/groups", + defaults: new { controller = "SimApiCoce", action = "ListGroups" }); + builder.MapControllerRoute(name: "LoginUseCoce", pattern: "/auth/config", + defaults: new { controller = "SimApiCoce", action = "GetConfig" }); + } } if (options.EnableSimApiDoc)