From ba1a0e86fd7d15d223e8a59b07da70e9d942291e Mon Sep 17 00:00:00 2001 From: NoahLan <6995syu@163.com> Date: Tue, 20 Feb 2024 15:29:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=A7=E6=8A=B5=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=88=9D=E7=89=88=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AuthenticationConstants.cs | 9 ++ .../AuthenticationErrCodeModel.cs | 20 +++ .../AuthenticationOAuthOptions.cs | 11 ++ .../Class1.cs | 5 - .../Gitee/GiteeAuthenticationConstants.cs | 10 ++ .../Gitee/GiteeAuthenticationDefaults.cs | 44 ++++++ .../Gitee/GiteeAuthenticationExtensions.cs | 66 ++++++++ .../Gitee/GiteeAuthenticationHandler.cs | 54 +++++++ .../Gitee/GiteeAuthenticationOptions.cs | 36 +++++ .../Gitee/GiteeAuthticationcationHttpModel.cs | 55 +++++++ ...workAspNetCoreAuthenticationOAuthModule.cs | 16 ++ .../OAuthAuthenticationHandler.cs | 108 +++++++++++++ .../QQ/QQAuthenticationConstants.cs | 17 ++ .../QQ/QQAuthenticationDefaults.cs | 47 ++++++ .../QQ/QQAuthenticationExtensions.cs | 67 ++++++++ .../QQ/QQAuthenticationHandler.cs | 64 ++++++++ .../QQ/QQAuthenticationOptions.cs | 40 +++++ .../QQ/QQAuthticationcationHttpModel.cs | 78 +++++++++ .../Class1.cs | 5 - .../IDeleteAppService.cs | 9 ++ .../INPinCrudAppService.cs | 30 ++++ .../IPageTimeResultRequestDto.cs | 9 ++ .../IPagedAllResultRequestDto.cs | 7 + ...nFrameworkDddApplicationContractsModule.cs | 10 ++ .../PagedAllResultRequestDto.cs | 16 ++ .../NPin.Framework.Ddd.Application/Class1.cs | 5 - .../NPinCrudAppService.cs | 148 ++++++++++++++++++ .../NPinFrameworkDddApplicationModule.cs | 18 +++ .../SqlSugarDbContext.cs | 13 ++ 29 files changed, 1002 insertions(+), 15 deletions(-) create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationConstants.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationErrCodeModel.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationOAuthOptions.cs delete mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Class1.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationConstants.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationDefaults.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationExtensions.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationHandler.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationOptions.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthticationcationHttpModel.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/NPinFrameworkAspNetCoreAuthenticationOAuthModule.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/OAuthAuthenticationHandler.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationConstants.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationDefaults.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationExtensions.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationHandler.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationOptions.cs create mode 100644 framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthticationcationHttpModel.cs delete mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/Class1.cs create mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/IDeleteAppService.cs create mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/INPinCrudAppService.cs create mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs create mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs create mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/NPinFrameworkDddApplicationContractsModule.cs create mode 100644 framework/NPin.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs delete mode 100644 framework/NPin.Framework.Ddd.Application/Class1.cs create mode 100644 framework/NPin.Framework.Ddd.Application/NPinCrudAppService.cs create mode 100644 framework/NPin.Framework.Ddd.Application/NPinFrameworkDddApplicationModule.cs diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationConstants.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationConstants.cs new file mode 100644 index 0000000..b5f2cb5 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationConstants.cs @@ -0,0 +1,9 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth; + +public class AuthenticationConstants +{ + public const string OpenId = "urn:openid"; + public const string UnionId = "urn:unionid"; + public const string AccessToken = "urn:access_token"; + public const string Name = "urn:name"; +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationErrCodeModel.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationErrCodeModel.cs new file mode 100644 index 0000000..7dbdd2d --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationErrCodeModel.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth; + +public class AuthenticationErrCodeModel +{ + [JsonProperty(PropertyName = "error")] public string Error { get; set; } + + [JsonProperty(PropertyName = "error_description")] + public string ErrorDescription { get; set; } + + public static void VerifyErrResponse(string content) + { + var model = JsonConvert.DeserializeObject(content); + if (model.Error != null) + { + throw new Exception($"第三方授权返回错误 错误码 [{model.Error}] 错误详情 [{model.ErrorDescription}]"); + } + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationOAuthOptions.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationOAuthOptions.cs new file mode 100644 index 0000000..11b1780 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/AuthenticationOAuthOptions.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authentication.OAuth; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth; + +public class AuthenticationOAuthOptions : OAuthOptions +{ + /// + /// 跳转地址 + /// + public string RedirectUri { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Class1.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Class1.cs deleted file mode 100644 index f8695cc..0000000 --- a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace NPin.Framework.AspNetCore.Authentication.OAuth; - -public class Class1 -{ -} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationConstants.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationConstants.cs new file mode 100644 index 0000000..f7af3ff --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationConstants.cs @@ -0,0 +1,10 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth.Gitee; + +public static class GiteeAuthenticationConstants +{ + public static class Claims + { + public const string Url = "urn:gitee:url"; + public const string AvatarUrl = "urn:gitee:avatarUrl"; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationDefaults.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationDefaults.cs new file mode 100644 index 0000000..71dd16d --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationDefaults.cs @@ -0,0 +1,44 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth.Gitee; + +public static class GiteeAuthenticationDefaults +{ + /// + /// Default value for . + /// + public const string AuthenticationScheme = "Gitee"; + + /// + /// Default value for . + /// + public static readonly string DisplayName = "Gitee"; + + /// + /// Default value for . + /// + public static readonly string Issuer = "Gitee"; + + /// + /// Default value for . + /// + public static readonly string CallbackPath = "/signin-gitee"; + + /// + /// Default value for . + /// + public static readonly string AuthorizationEndpoint = "https://gitee.com/oauth/authorize"; + + /// + /// Default value for . + /// + public static readonly string TokenEndpoint = "https://gitee.com/oauth/token"; + + /// + /// Default value for . + /// + public static readonly string UserInformationEndpoint = "https://gitee.com/api/v5/user"; + + /// + /// Default value for . + /// + public static readonly string UserEmailsEndpoint = "https://gitee.com/api/v5/emails"; +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationExtensions.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationExtensions.cs new file mode 100644 index 0000000..5485ca2 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationExtensions.cs @@ -0,0 +1,66 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authentication; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth.Gitee; + +public static class GiteeAuthenticationExtensions +{ + /// + /// Adds to the specified + /// , which enables Gitee authentication capabilities. + /// + /// The authentication builder. + /// The . + public static AuthenticationBuilder AddGitee([NotNull] this AuthenticationBuilder builder) + { + return builder.AddGitee(GiteeAuthenticationDefaults.AuthenticationScheme, options => { }); + } + + /// + /// Adds to the specified + /// , which enables Gitee authentication capabilities. + /// + /// The authentication builder. + /// The delegate used to configure the OpenID 2.0 options. + /// The . + public static AuthenticationBuilder AddGitee( + [NotNull] this AuthenticationBuilder builder, + [NotNull] Action configuration) + { + return builder.AddGitee(GiteeAuthenticationDefaults.AuthenticationScheme, configuration); + } + + /// + /// Adds to the specified + /// , which enables Gitee authentication capabilities. + /// + /// The authentication builder. + /// The authentication scheme associated with this instance. + /// The delegate used to configure the Gitee options. + /// The . + public static AuthenticationBuilder AddGitee( + [NotNull] this AuthenticationBuilder builder, + [NotNull] string scheme, + [NotNull] Action configuration) + { + return builder.AddGitee(scheme, GiteeAuthenticationDefaults.DisplayName, configuration); + } + + /// + /// Adds to the specified + /// , which enables Gitee authentication capabilities. + /// + /// The authentication builder. + /// The authentication scheme associated with this instance. + /// The optional display name associated with this instance. + /// The delegate used to configure the Gitee options. + /// The . + public static AuthenticationBuilder AddGitee( + [NotNull] this AuthenticationBuilder builder, + [NotNull] string scheme, + [CanBeNull] string caption, + [NotNull] Action configuration) + { + return builder.AddScheme(scheme, caption, configuration); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationHandler.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationHandler.cs new file mode 100644 index 0000000..a2b0221 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationHandler.cs @@ -0,0 +1,54 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using static NPin.Framework.AspNetCore.Authentication.OAuth.Gitee.GiteeAuthenticationConstants; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth.Gitee; + +public class GiteeAuthenticationHandler : OAuthAuthenticationHandler +{ + public GiteeAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, + UrlEncoder encoder, IHttpClientFactory httpClientFactory) : base(options, logger, encoder, httpClientFactory) + { + } + + public override string AuthenticationSchemeName => GiteeAuthenticationDefaults.AuthenticationScheme; + + protected override async Task> GetAuthTicketAsync(string code) + { + // 获取 accessToken + var tokenQueryKv = new List> + { + new("grant_type", "authorization_code"), + new("client_id", Options.ClientId), + new("client_secret", Options.ClientSecret), + new("redirect_uri", Options.RedirectUri), + new("code", code) + }; + var tokenModel = + await SendHttpRequestAsync(GiteeAuthenticationDefaults.TokenEndpoint, + tokenQueryKv, HttpMethod.Post); + + // 获取 userInfo + var userInfoQueryKv = new List> + { + new("access_token", tokenModel.access_token), + }; + + var userInfoModel = + await SendHttpRequestAsync( + GiteeAuthenticationDefaults.UserInformationEndpoint, userInfoQueryKv); + + List claims = + [ + new(Claims.AvatarUrl, userInfoModel.avatar_url), + new(Claims.Url, userInfoModel.url), + + new(AuthenticationConstants.OpenId, userInfoModel.id.ToString()), + new(AuthenticationConstants.Name, userInfoModel.name), + new(AuthenticationConstants.AccessToken, tokenModel.access_token) + ]; + return claims; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationOptions.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationOptions.cs new file mode 100644 index 0000000..6f62ece --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthenticationOptions.cs @@ -0,0 +1,36 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; + +using static NPin.Framework.AspNetCore.Authentication.OAuth.Gitee.GiteeAuthenticationConstants; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth.Gitee; + +public class GiteeAuthenticationOptions: AuthenticationOAuthOptions +{ + public GiteeAuthenticationOptions() + { + ClaimsIssuer = GiteeAuthenticationDefaults.Issuer; + + CallbackPath = GiteeAuthenticationDefaults.CallbackPath; + + AuthorizationEndpoint = GiteeAuthenticationDefaults.AuthorizationEndpoint; + TokenEndpoint = GiteeAuthenticationDefaults.TokenEndpoint; + UserInformationEndpoint = GiteeAuthenticationDefaults.UserInformationEndpoint; + UserEmailsEndpoint = GiteeAuthenticationDefaults.UserEmailsEndpoint; + + Scope.Add("user_info"); + Scope.Add("emails"); + + ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id"); + ClaimActions.MapJsonKey(ClaimTypes.Name, "login"); + ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); + ClaimActions.MapJsonKey(ClaimTypes.Name, "name"); + ClaimActions.MapJsonKey(Claims.Url, "url"); + } + + /// + /// Gets or sets the address of the endpoint exposing + /// the email addresses associated with the logged in user. + /// + public string UserEmailsEndpoint { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthticationcationHttpModel.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthticationcationHttpModel.cs new file mode 100644 index 0000000..99faa79 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/Gitee/GiteeAuthticationcationHttpModel.cs @@ -0,0 +1,55 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth.Gitee; + +public class GiteeAuthticationcationTokenResponse +{ + public string access_token { get; set; } + public string token_type { get; set; } + public int expires_in { get; set; } + public string refresh_token { get; set; } + public string scope { get; set; } + public long created_at { get; set; } +} + + +public class GiteeAuthticationcationOpenIdResponse +{ + public string client_id { get; set; } + public string openid { get; set; } + +} + +public class GiteeAuthticationcationUserInfoResponse +{ + /// + /// 也可以等于openId + /// + public int id { get; set; } + public string login { get; set; } + public string name { get; set; } + public string avatar_url { get; set; } + public string url { get; set; } + public string html_url { get; set; } + public string remark { get; set; } + public string followers_url { get; set; } + public string following_url { get; set; } + public string gists_url { get; set; } + public string starred_url { get; set; } + public string subscriptions_url { get; set; } + public string organizations_url { get; set; } + public string repos_url { get; set; } + public string events_url { get; set; } + public string received_events_url { get; set; } + public string type { get; set; } + public string blog { get; set; } + public string weibo { get; set; } + public string bio { get; set; } + public int public_repos { get; set; } + public int public_gists { get; set; } + public int followers { get; set; } + public int following { get; set; } + public int stared { get; set; } + public int watched { get; set; } + public DateTime created_at { get; set; } + public DateTime updated_at { get; set; } + public string email { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/NPinFrameworkAspNetCoreAuthenticationOAuthModule.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/NPinFrameworkAspNetCoreAuthenticationOAuthModule.cs new file mode 100644 index 0000000..1b773ca --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/NPinFrameworkAspNetCoreAuthenticationOAuthModule.cs @@ -0,0 +1,16 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Modularity; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth; + +/// +/// AspNet.Security.OAuth.QQ +/// +[DependsOn(typeof(NPinFrameworkAspNetCoreModule))] +public class NPinFrameworkAspNetCoreAuthenticationOAuthModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddHttpClient(); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/OAuthAuthenticationHandler.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/OAuthAuthenticationHandler.cs new file mode 100644 index 0000000..b5bd7d5 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/OAuthAuthenticationHandler.cs @@ -0,0 +1,108 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth; + +public abstract class OAuthAuthenticationHandler : AuthenticationHandler + where TOptions : AuthenticationSchemeOptions, new() +{ + public abstract string AuthenticationSchemeName { get; } + // private AuthenticationScheme _scheme; + + protected IHttpClientFactory HttpClientFactory { get; } + protected HttpClient HttpClient { get; } + + public OAuthAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, + IHttpClientFactory httpClientFactory) : + base(options, logger, encoder) + { + HttpClientFactory = httpClientFactory; + HttpClient = httpClientFactory.CreateClient(); + } + + /// + /// Claims 转换为 票据 + /// + /// + /// + private AuthenticationTicket ConvertTicket(List claims) + { + var claimsIdentity = new ClaimsIdentity(claims.ToArray(), AuthenticationSchemeName); + var principal = new ClaimsPrincipal(claimsIdentity); + return new AuthenticationTicket(principal, AuthenticationSchemeName); + } + + protected async Task SendHttpRequestAsync(string url, + IEnumerable> query, + HttpMethod? httpMethod = null) + { + httpMethod ??= HttpMethod.Get; + + var queryUrl = QueryHelpers.AddQueryString(url, query); + HttpResponseMessage response = null; + if (httpMethod == HttpMethod.Get) + { + response = await HttpClient.GetAsync(queryUrl); + } + else if (httpMethod == HttpMethod.Post) + { + response = await HttpClient.PostAsync(queryUrl, null); + } + + if (response == null) + { + throw new NotSupportedException($"不支持的请求方式: {httpMethod}"); + } + + var content = await response.Content.ReadAsStringAsync(); + if (!response.IsSuccessStatusCode) + { + throw new Exception($"授权服务器请求错误,请求地址[{queryUrl}] 错误信息[{content}]"); + } + + VerifyErrResponse(content); + + var model = JsonConvert.DeserializeObject(content); + return model!; + } + + protected virtual void VerifyErrResponse(string content) + { + AuthenticationErrCodeModel.VerifyErrResponse(content); + } + + /// + /// 获取登录票据 + /// + /// + /// + protected abstract Task> GetAuthTicketAsync(string code); + + protected override async Task HandleAuthenticateAsync() + { + if (!Context.Request.Query.ContainsKey("code")) + { + return AuthenticateResult.Fail("回调中未包含code参数"); + } + + var code = Context.Request.Query["code"].ToString(); + + List authTicket; + try + { + authTicket = await GetAuthTicketAsync(code); + } + catch (Exception ex) + { + return AuthenticateResult.Fail(ex.Message ?? "未知错误"); + } + + // 获取登录票据成功 + return AuthenticateResult.Success(ConvertTicket(authTicket)); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationConstants.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationConstants.cs new file mode 100644 index 0000000..27ad498 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationConstants.cs @@ -0,0 +1,17 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth.QQ; + +/// +/// QQ登录的常量 +/// +public static class QQAuthenticationConstants +{ + public static class Claims + { + public const string AvatarFullUrl = "urn:qq:avatar_full"; + public const string AvatarUrl = "urn:qq:avatar"; + public const string PictureFullUrl = "urn:qq:picture_full"; + public const string PictureMediumUrl = "urn:qq:picture_medium"; + public const string PictureUrl = "urn:qq:picture"; + public const string UnionId = "urn:qq:unionid"; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationDefaults.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationDefaults.cs new file mode 100644 index 0000000..d011771 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationDefaults.cs @@ -0,0 +1,47 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth.QQ; + +/// +/// Default values for QQ authentication. +/// +public class QQAuthenticationDefaults +{ + /// + /// Default value for . + /// + public const string AuthenticationScheme = "QQ"; + + /// + /// Default value for . + /// + public static readonly string DisplayName = "QQ"; + + /// + /// Default value for . + /// + public static readonly string Issuer = "QQ"; + + /// + /// Default value for . + /// + public static readonly string CallbackPath = "/signin-qq"; + + /// + /// Default value for . + /// + public static readonly string AuthorizationEndpoint = "https://graph.qq.com/oauth2.0/authorize"; + + /// + /// Default value for . + /// + public static readonly string TokenEndpoint = "https://graph.qq.com/oauth2.0/token"; + + /// + /// Default value for . + /// + public static readonly string UserIdentificationEndpoint = "https://graph.qq.com/oauth2.0/me"; + + /// + /// Default value for . + /// + public static readonly string UserInformationEndpoint = "https://graph.qq.com/user/get_user_info"; +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationExtensions.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationExtensions.cs new file mode 100644 index 0000000..511d795 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationExtensions.cs @@ -0,0 +1,67 @@ +using JetBrains.Annotations; +using Microsoft.AspNetCore.Authentication; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth.QQ; + +public static class QQAuthenticationExtensions +{ + /// + /// Adds to the specified + /// , which enables QQ authentication capabilities. + /// + /// The authentication builder. + /// The . + public static AuthenticationBuilder AddQQ([NotNull] this AuthenticationBuilder builder) + { + return builder.AddQQ(QQAuthenticationDefaults.AuthenticationScheme, options => { }); + } + + /// + /// Adds to the specified + /// , which enables QQ authentication capabilities. + /// + /// The authentication builder. + /// The delegate used to configure the OpenID 2.0 options. + /// The . + public static AuthenticationBuilder AddQQ( + [NotNull] this AuthenticationBuilder builder, + [NotNull] Action configuration) + { + return builder.AddQQ(QQAuthenticationDefaults.AuthenticationScheme, configuration); + } + + /// + /// Adds to the specified + /// , which enables QQ authentication capabilities. + /// + /// The authentication builder. + /// The authentication scheme associated with this instance. + /// The delegate used to configure the QQ options. + /// The . + public static AuthenticationBuilder AddQQ( + [NotNull] this AuthenticationBuilder builder, + [NotNull] string scheme, + [NotNull] Action configuration) + { + return builder.AddQQ(scheme, QQAuthenticationDefaults.DisplayName, configuration); + } + + /// + /// Adds to the specified + /// , which enables QQ authentication capabilities. + /// + /// The authentication builder. + /// The authentication scheme associated with this instance. + /// The optional display name associated with this instance. + /// The delegate used to configure the QQ options. + /// The . + public static AuthenticationBuilder AddQQ( + [NotNull] this AuthenticationBuilder builder, + [NotNull] string scheme, + [CanBeNull] string caption, + [NotNull] Action configuration) + { + return builder.AddScheme(scheme, caption, configuration); + + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationHandler.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationHandler.cs new file mode 100644 index 0000000..ee2adec --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationHandler.cs @@ -0,0 +1,64 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using static NPin.Framework.AspNetCore.Authentication.OAuth.QQ.QQAuthenticationConstants; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth.QQ; + +public class QQAuthenticationHandler : OAuthAuthenticationHandler +{ + public QQAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, + UrlEncoder encoder, IHttpClientFactory httpClientFactory) : + base(options, logger, encoder, httpClientFactory) + { + } + + public override string AuthenticationSchemeName => QQAuthenticationDefaults.AuthenticationScheme; + + protected override async Task> GetAuthTicketAsync(string code) + { + // 获取 AccessToken + var tokenQueryKv = new List> + { + new("grant_type", "authorization_code"), + new("client_id", Options.ClientId), + new("client_secret", Options.ClientSecret), + new("redirect_uri", Options.RedirectUri), + new("fmt", "json"), + new("need_openid", "1"), + new("code", code) + }; + var tokenModel = + await SendHttpRequestAsync(QQAuthenticationDefaults.TokenEndpoint, + tokenQueryKv); + + // 获取 userinfo + var userInfoQueryKv = new List> + { + new("access_token", tokenModel.access_token), + new("oauth_consumer_key", Options.ClientId), + new("openid", tokenModel.openid), + }; + + var userInfoModel = + await SendHttpRequestAsync( + QQAuthenticationDefaults.UserInformationEndpoint, userInfoQueryKv); + + // 组装 claims + var claims = new List + { + new(Claims.AvatarFullUrl, userInfoModel.figureurl_qq_2), + new(Claims.AvatarUrl, userInfoModel.figureurl_qq_1), + new(Claims.PictureFullUrl, userInfoModel.figureurl_2), + new(Claims.PictureMediumUrl, userInfoModel.figureurl_qq_1), + new(Claims.PictureUrl, userInfoModel.figureurl), + + new(AuthenticationConstants.OpenId, tokenModel.openid), + new(AuthenticationConstants.Name, userInfoModel.nickname), + new(AuthenticationConstants.AccessToken, tokenModel.access_token), + }; + + return claims; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationOptions.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationOptions.cs new file mode 100644 index 0000000..d3448d3 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthenticationOptions.cs @@ -0,0 +1,40 @@ +using System.Security.Claims; +using Microsoft.AspNetCore.Authentication; + +using static NPin.Framework.AspNetCore.Authentication.OAuth.QQ.QQAuthenticationConstants; + +namespace NPin.Framework.AspNetCore.Authentication.OAuth.QQ; + +public class QQAuthenticationOptions: AuthenticationOAuthOptions +{ + public QQAuthenticationOptions() + { + ClaimsIssuer = QQAuthenticationDefaults.Issuer; + CallbackPath = QQAuthenticationDefaults.CallbackPath; + + AuthorizationEndpoint = QQAuthenticationDefaults.AuthorizationEndpoint; + TokenEndpoint = QQAuthenticationDefaults.TokenEndpoint; + UserIdentificationEndpoint = QQAuthenticationDefaults.UserIdentificationEndpoint; + UserInformationEndpoint = QQAuthenticationDefaults.UserInformationEndpoint; + + Scope.Add("get_user_info"); + + ClaimActions.MapJsonKey(ClaimTypes.Name, "nickname"); + ClaimActions.MapJsonKey(ClaimTypes.Gender, "gender"); + ClaimActions.MapJsonKey(Claims.PictureUrl, "figureurl"); + ClaimActions.MapJsonKey(Claims.PictureMediumUrl, "figureurl_1"); + ClaimActions.MapJsonKey(Claims.PictureFullUrl, "figureurl_2"); + ClaimActions.MapJsonKey(Claims.AvatarUrl, "figureurl_qq_1"); + ClaimActions.MapJsonKey(Claims.AvatarFullUrl, "figureurl_qq_2"); + } + + /// + /// Gets or sets if the union Id (the primary key of an owner for different apps of the QQ platform) should be put into the user claims. + /// + public bool ApplyForUnionId { get; set; } + + /// + /// Gets or sets the URL of the user identification endpoint (a.k.a. the "OpenID endpoint"). + /// + public string UserIdentificationEndpoint { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthticationcationHttpModel.cs b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthticationcationHttpModel.cs new file mode 100644 index 0000000..9aa4549 --- /dev/null +++ b/framework/NPin.Framework.AspNetCore.Authentication.OAuth/QQ/QQAuthticationcationHttpModel.cs @@ -0,0 +1,78 @@ +namespace NPin.Framework.AspNetCore.Authentication.OAuth.QQ; + +public class QQAuthticationcationTokenResponse +{ + public string access_token { get; set; } + public string expires_in { get; set; } + public string refresh_token { get; set; } + public string openid { get; set; } +} + + +public class QQAuthticationcationOpenIdResponse +{ + public string client_id { get; set; } + public string openid { get; set; } + +} + +public class QQAuthticationcationUserInfoResponse +{ + // 返回码 + public int ret { get; set; } + + // 如果ret<0,会有相应的错误信息提示 + // 返回数据全部用UTF-8编码 + public string msg { get; set; } + + // 判断是否有数据丢失 + // 0或者不返回:没有数据丢失,可以缓存 + // 1:有部分数据丢失或错误,不要缓存 + public int is_lost { get; set; } + + // 用户在QQ空间的昵称 + public string nickname { get; set; } + + // 大小为30x30像素的QQ空间头像URL + public string figureurl { get; set; } + + // 大小为50x50像素的QQ空间头像URL + public string figureurl_1 { get; set; } + + // 大小为100x100像素的QQ空间头像URL + public string figureurl_2 { get; set; } + + // 大小为40x40像素的QQ头像URL + public string figureurl_qq_1 { get; set; } + + // 大小为100x100像素的QQ头像URL + // 需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有 + public string figureurl_qq_2 { get; set; } + + // 性别。如果获取不到则默认返回"男" + public string gender { get; set; } + + // 性别类型。默认返回2 + public int gender_type { get; set; } + + // 省 + public string province { get; set; } + + // 市 + public string city { get; set; } + + // 年 + public int year { get; set; } + + // 星座 + public string constellation { get; set; } + + // 标识用户是否为黄钻用户 + public int is_yellow_vip { get; set; } + + // 黄钻等级 + public int yellow_vip_level { get; set; } + + // 是否为年费黄钻用户 + public int is_yellow_year_vip { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/Class1.cs b/framework/NPin.Framework.Ddd.Application.Contracts/Class1.cs deleted file mode 100644 index 3f46dc4..0000000 --- a/framework/NPin.Framework.Ddd.Application.Contracts/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace NPin.Framework.Ddd.Application.Contracts; - -public class Class1 -{ -} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/IDeleteAppService.cs b/framework/NPin.Framework.Ddd.Application.Contracts/IDeleteAppService.cs new file mode 100644 index 0000000..d397ee7 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application.Contracts/IDeleteAppService.cs @@ -0,0 +1,9 @@ +using Volo.Abp; +using Volo.Abp.Application.Services; + +namespace NPin.Framework.Ddd.Application.Contracts; + +public interface IDeleteAppService : Volo.Abp.Application.Services.IDeleteAppService +{ + Task DeleteAsync(IEnumerable ids); +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/INPinCrudAppService.cs b/framework/NPin.Framework.Ddd.Application.Contracts/INPinCrudAppService.cs new file mode 100644 index 0000000..4c1bad0 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application.Contracts/INPinCrudAppService.cs @@ -0,0 +1,30 @@ +using Volo.Abp.Application.Services; + +namespace NPin.Framework.Ddd.Application.Contracts; + +public interface INPinCrudAppService : ICrudAppService +{ +} + +public interface + INPinCrudAppService : ICrudAppService +{ +} + +public interface + INPinCrudAppService : ICrudAppService +{ +} + +public interface INPinCrudAppService : + ICrudAppService +{ +} + +public interface INPinCrudAppService : + ICrudAppService, + IDeleteAppService +{ +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs b/framework/NPin.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs new file mode 100644 index 0000000..2e219e9 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application.Contracts/IPageTimeResultRequestDto.cs @@ -0,0 +1,9 @@ +using Volo.Abp.Application.Dtos; + +namespace NPin.Framework.Ddd.Application.Contracts; + +public interface IPageTimeResultRequestDto : IPagedAndSortedResultRequest +{ + DateTime? StartTime { get; set; } + DateTime? EndTime { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs b/framework/NPin.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs new file mode 100644 index 0000000..446861a --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application.Contracts/IPagedAllResultRequestDto.cs @@ -0,0 +1,7 @@ +using Volo.Abp.Application.Dtos; + +namespace NPin.Framework.Ddd.Application.Contracts; + +public interface IPagedAllResultRequestDto : IPageTimeResultRequestDto, IPagedAndSortedResultRequest +{ +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/NPinFrameworkDddApplicationContractsModule.cs b/framework/NPin.Framework.Ddd.Application.Contracts/NPinFrameworkDddApplicationContractsModule.cs new file mode 100644 index 0000000..d75fb63 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application.Contracts/NPinFrameworkDddApplicationContractsModule.cs @@ -0,0 +1,10 @@ +using Volo.Abp.Application; +using Volo.Abp.Modularity; + +namespace NPin.Framework.Ddd.Application.Contracts; + +[DependsOn(typeof(AbpDddApplicationContractsModule))] +public class NPinFrameworkDddApplicationContractsModule: AbpModule +{ + +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs b/framework/NPin.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs new file mode 100644 index 0000000..df0e9c8 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application.Contracts/PagedAllResultRequestDto.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Application.Dtos; + +namespace NPin.Framework.Ddd.Application.Contracts; + +public class PagedAllResultRequestDto : PagedAndSortedResultRequestDto, IPagedAllResultRequestDto +{ + /// + /// 查询开始时间条件 + /// + public DateTime? StartTime { get; set; } + + /// + /// 查询结束时间条件 + /// + public DateTime? EndTime { get; set; } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application/Class1.cs b/framework/NPin.Framework.Ddd.Application/Class1.cs deleted file mode 100644 index cf637de..0000000 --- a/framework/NPin.Framework.Ddd.Application/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace NPin.Framework.Ddd.Application; - -public class Class1 -{ -} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application/NPinCrudAppService.cs b/framework/NPin.Framework.Ddd.Application/NPinCrudAppService.cs new file mode 100644 index 0000000..1c96667 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application/NPinCrudAppService.cs @@ -0,0 +1,148 @@ +using Microsoft.AspNetCore.Mvc; +using MiniExcelLibs; +using Volo.Abp; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Entities; +using Volo.Abp.Domain.Repositories; + +namespace NPin.Framework.Ddd.Application; + +public abstract class + NPinCrudAppService : NPinCrudAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto +{ + protected NPinCrudAppService(IRepository repository) : base(repository) + { + } +} + +public abstract class NPinCrudAppService + : NPinCrudAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto +{ + protected NPinCrudAppService(IRepository repository) : base(repository) + { + } +} + +public abstract class NPinCrudAppService + : NPinCrudAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto +{ + protected NPinCrudAppService(IRepository repository) : base(repository) + { + } +} + +public abstract class NPinCrudAppService + : NPinCrudAppService + where TEntity : class, IEntity + where TEntityDto : IEntityDto +{ + protected NPinCrudAppService(IRepository repository) : base(repository) + { + } +} + +public abstract class NPinCrudAppService : + CrudAppService + where TEntity : class, IEntity + where TGetOutputDto : IEntityDto + where TGetListOutputDto : IEntityDto +{ + protected NPinCrudAppService(IRepository repository) : base(repository) + { + } + + /// + /// 多查/批量查 + /// + /// + /// + public override async Task> GetListAsync(TGetListInput input) + { + List entities; + // 区分查/批量查 + if (input is IPagedResultRequest pagedInput) + { + entities = await Repository.GetPagedListAsync(pagedInput.SkipCount, pagedInput.MaxResultCount, + string.Empty); + } + else + { + entities = await Repository.GetListAsync(); + } + + var total = await Repository.GetCountAsync(); + var output = await MapToGetListOutputDtosAsync(entities); + return new PagedResultDto(total, output); + } + + /// + /// 多删 + /// + /// + [RemoteService(isEnabled: true)] + public virtual async Task DeleteAsync(IEnumerable ids) + { + await Repository.DeleteManyAsync(ids); + } + + /// + /// 单删(覆盖) + /// + /// + /// + [RemoteService(isEnabled: false)] + public override Task DeleteAsync(TKey id) + { + return base.DeleteAsync(id); + } + + /// + /// 导出Excel + /// + /// + /// + public virtual async Task GetExportExcelAsync(TGetListInput input) + { + if (input is IPagedResultRequest paged) + { + // 不进行分页 + paged.SkipCount = 0; + paged.MaxResultCount = LimitedResultRequestDto.MaxMaxResultCount; + } + + var output = await this.GetListAsync(input); + var dirPath = $"/temp"; + + var filename = $"{typeof(TEntity).Name}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{Guid.NewGuid()}"; + var filePath = $"{dirPath}/{filename}.xlsx"; + if (!Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + + await MiniExcel.SaveAsAsync(filePath, output.Items); + return new PhysicalFileResult(filePath, "application/vnd.ms-excel"); + } + + /// + /// 导入Excel + /// + /// + public virtual async Task PostImportExcelAsync(List input) + { + // var entities = input.Select(MapToEntity).ToList(); + // await Repository.InsertManyAsync(entities); + + // 需子类自行实现 + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.Ddd.Application/NPinFrameworkDddApplicationModule.cs b/framework/NPin.Framework.Ddd.Application/NPinFrameworkDddApplicationModule.cs new file mode 100644 index 0000000..6e84cc3 --- /dev/null +++ b/framework/NPin.Framework.Ddd.Application/NPinFrameworkDddApplicationModule.cs @@ -0,0 +1,18 @@ +using NPin.Framework.Ddd.Application.Contracts; +using Volo.Abp; +using Volo.Abp.Application; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Modularity; + +namespace NPin.Framework.Ddd.Application; + +[DependsOn(typeof(AbpDddApplicationModule), typeof(NPinFrameworkDddApplicationContractsModule))] +public class NPinFrameworkDddApplicationModule : AbpModule +{ + public override void OnApplicationInitialization(ApplicationInitializationContext context) + { + // 分页限制调整 + LimitedResultRequestDto.DefaultMaxResultCount = 10; + LimitedResultRequestDto.MaxMaxResultCount = 10000; + } +} \ No newline at end of file diff --git a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs index 81280df..98846e8 100644 --- a/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs +++ b/framework/NPin.Framework.SqlSugarCore/SqlSugarDbContext.cs @@ -202,8 +202,21 @@ public class SqlSugarDbContext : ISqlSugarDbContext { } + /// + /// 实体配置 + /// + /// + /// protected virtual void EntityService(PropertyInfo property, EntityColumnInfo column) { + if (property.PropertyType == typeof(ExtraPropertyDictionary)) + { + column.IsIgnore = true; + } + if (property.Name == nameof(Entity.Id)) + { + column.IsPrimarykey = true; + } } public void Backup()