添加项目文件。

This commit is contained in:
2025-12-02 13:10:10 +08:00
parent 93a2382a16
commit 289aa4cbe7
400 changed files with 91177 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
using Microsoft.AspNetCore.Components;
namespace Atomx.Admin.Client.Utils
{
public class AuthHeaderHandler : DelegatingHandler
{
private readonly ITokenProvider _tokenProvider;
private readonly NavigationManager _navigationManager;
private readonly ILogger<AuthHeaderHandler> _logger;
public AuthHeaderHandler(
ITokenProvider tokenProvider,
NavigationManager navigationManager,
ILogger<AuthHeaderHandler> logger)
{
_tokenProvider = tokenProvider;
_navigationManager = navigationManager;
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
// 获取token
var token = await _tokenProvider.GetTokenAsync();
if (!string.IsNullOrEmpty(token))
{
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
else
{
_logger.LogWarning("No authentication token available for request: {Url}", request.RequestUri);
}
var response = await base.SendAsync(request, cancellationToken);
// 处理认证失败
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await HandleUnauthorizedAsync();
}
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error sending HTTP request to {Url}", request.RequestUri);
throw;
}
}
private async Task HandleUnauthorizedAsync()
{
// 在WASM模式下重定向到登录页
if (OperatingSystem.IsBrowser())
{
_navigationManager.NavigateTo("/account/login", true);
}
// 在Server模式下可以执行其他操作
else
{
// Server端的处理逻辑
_logger.LogWarning("Unauthorized access detected in server mode");
}
}
}
}

View File

@@ -0,0 +1,20 @@
using Atomx.Admin.Client.Services;
namespace Atomx.Admin.Client.Utils
{
public static class HttpClientExtension
{
public static void AddHttpClientApiService(this IServiceCollection services, string baseAddress)
{
services.AddHttpClient<HttpService>(client =>
{
client.BaseAddress = new Uri(baseAddress);
client.DefaultRequestHeaders.Add("Accept", "application/json");
}).AddHttpMessageHandler<AuthHeaderHandler>();
//services.AddHttpClient<AdminLoginLogService>(client => client.BaseAddress = new Uri(baseAddress));
//services.AddHttpClient<AdminService>(client => client.BaseAddress = new Uri(baseAddress));
}
}
}

View File

@@ -0,0 +1,198 @@
using AntDesign;
namespace Atomx.Admin.Client.Utils
{
public class IconsExtension
{
public static List<string> IconList()
{
return new List<string>()
{
"step-backward",
"step-forward",
"fast-backward",
"fast-forward",
"shrink",
"arrows-alt",
"down",
"up",
"left",
"right",
"caret-up",
"caret-down",
"caret-left",
"caret-right",
"up-circle",
"down-circle",
"left-circle",
"right-circle",
"double-right",
"double-left",
"vertical-left",
"vertical-right",
"vertical-align-top",
"vertical-align-middle",
"vertical-align-bottom",
"forward",
"backward",
"rollback",
"enter",
"retweet",
"swap",
"swap-left",
"swap-right",
"arrow-up",
"arrow-down",
"arrow-left",
"arrow-right",
"play-circle",
"up-square",
"down-square",
"left-square",
"right-square",
"login",
"logout",
"menu-fold",
"menu-unfold",
"border-bottom",
"border-horizontal",
"border-inner",
"border-outer",
"border-left",
"border-right",
"border-top",
"border-verticle",
"pic-center",
"pic-left",
"pic-right",
"radius-bottomleft",
"radius-bottomright",
"radius-upleft",
"radius-upright",
"fullscreen",
"fullscreen-exit",
"question",
"question-circle",
"plus",
"plus-circle",
"pause",
"pause-circle",
"minus",
"minus-circle",
"plus-square",
"minus-square",
"info",
"info-circle",
"exclamation",
"exclamation-circle",
"close",
"close-circle",
"close-square",
"check",
"check-circle",
"check-square",
"clock-circle",
"warning",
"issues-close",
"stop",
"edit",
"form",
"copy",
"scissor",
"delete",
"snippets",
"diff",
"highlight",
"align-center",
"align-left",
"align-right",
"bg-colors",
"bold",
"italic",
"underline",
"strikethrough",
"redo",
"undo",
"zoom-in",
"zoom-out",
"font-colors",
"font-size",
"line-height",
"colum-height",
"colum-width",
"dash",
"small-dash",
"sort-ascending",
"sort-descending",
"drag",
"ordered-list",
"unordered-list",
"radius-setting",
"column-width",
"area-chart",
"pie-chart",
"bar-chart",
"dot-chart",
"line-chart",
"radar-chart",
"heat-map",
"fall",
"rise",
"stock",
"box-plot",
"fund",
"sliders",
"android",
"apple",
"windows",
"ie",
"chrome",
"github",
"aliwangwang",
"dingding",
"weibo-square",
"weibo-circle",
"taobao-circle",
"html5",
"weibo",
"twitter",
"wechat",
"youtube",
"alipay-circle",
"taobao",
"skype",
"qq",
"medium-workmark",
"gitlab",
"medium",
"linkedin",
"google-plus",
"dropbox",
"facebook",
"codepen",
"code-sandbox",
"code-sandbox-circle",
"amazon",
"google",
"codepen-circle",
"alipay",
"ant-design",
"ant-cloud",
"aliyun",
"zhihu",
"slack",
"slack-square",
"behance",
"behance-square",
"dribbble",
"dribbble-square",
"instagram",
"yuque",
"alibaba",
"yahoo",
"reddit",
"sketch"
};
}
}
}

View File

@@ -0,0 +1,141 @@
using Blazored.LocalStorage;
using Atomx.Common.Configuration;
using Atomx.Common.Utils;
using Atomx.Utils.Extension;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace Atomx.Admin.Client.Utils
{
public class PersistentAuthenticationStateProvider : AuthenticationStateProvider
{
readonly ClaimsPrincipal anonymous = new(new ClaimsIdentity());
static readonly Task<AuthenticationState> defaultUnauthenticatedTask = Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
readonly Task<AuthenticationState> authenticationStateTask = defaultUnauthenticatedTask;
readonly ILocalStorageService _localStorage;
public PersistentAuthenticationStateProvider(PersistentComponentState state, ILocalStorageService localStorageService)
{
_localStorage = localStorageService;
if (!state.TryTakeFromJson<UserInfo>(nameof(UserInfo), out var userInfo) || userInfo is null)
{
return;
}
var claims = new List<Claim>
{
new(ClaimKeys.Id, userInfo.Id.ToString()),
new(ClaimKeys.Name, userInfo.Name),
new(ClaimKeys.Email, userInfo.Email),
new(ClaimKeys.Mobile, userInfo.MobilePhone),
new(ClaimKeys.Role, userInfo.Role),
};
foreach (var role in userInfo.Permissions)
{
claims.Add(new Claim(ClaimKeys.Permission, role));
}
authenticationStateTask = Task.FromResult(
new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType: nameof(PersistentAuthenticationStateProvider)))));
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var jwtToken = await _localStorage.GetItemAsStringAsync(StorageKeys.JWTTokenKeyName);
if (string.IsNullOrEmpty(jwtToken))
return await Task.FromResult(new AuthenticationState(anonymous));
else
{
var getUserClaims = DecryptToken(jwtToken);
if (getUserClaims == null)
return await Task.FromResult(new AuthenticationState(anonymous));
else
{
var claimsPrincipal = SetClaimPrincipal(getUserClaims);
return await Task.FromResult(new AuthenticationState(claimsPrincipal));
}
}
}
catch
{
return await Task.FromResult(new AuthenticationState(anonymous));
}
}
public ClaimsPrincipal SetClaimPrincipal(UserInfo customUserClaims)
{
if (string.IsNullOrEmpty(customUserClaims.Name))
return new ClaimsPrincipal();
else
{
var claims = new List<Claim>
{
new(ClaimKeys.Id, customUserClaims.Id.ToString()),
new(ClaimKeys.Name, customUserClaims.Name),
new(ClaimKeys.Email, customUserClaims.Email),
new(ClaimKeys.Mobile, customUserClaims.MobilePhone),
new(ClaimKeys.Role, customUserClaims.Role.ToString()),
};
foreach (var role in customUserClaims.Permissions)
{
claims.Add(new Claim(ClaimKeys.Permission, role));
}
return new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType: nameof(PersistentAuthenticationStateProvider)));
}
}
public void UpdateAuthenticationState(string jwtToken = "")
{
var claimsPrincipal = new ClaimsPrincipal();
if (!string.IsNullOrEmpty(jwtToken))
{
var getUserClaims = DecryptToken(jwtToken);
claimsPrincipal = SetClaimPrincipal(getUserClaims);
}
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
}
private UserInfo DecryptToken(string jwtToken)
{
if (string.IsNullOrEmpty(jwtToken))
return new UserInfo();
else
{
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(jwtToken);
var id = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Id)?.Value ?? string.Empty;
var name = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Name)?.Value ?? string.Empty;
var email = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Email)?.Value ?? string.Empty;
var phone = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Mobile)?.Value ?? string.Empty;
var role = token.Claims.SingleOrDefault(x => x.Type == ClaimKeys.Role)?.Value ?? string.Empty;
var permissions = token.Claims.Where(x => x.Type == ClaimKeys.Permission).Select(s => s.Value).ToArray();
return new UserInfo(id.ToLong(), name, email, phone, role, permissions);
}
}
public async Task MarkUserAsLoggedOut()
{
await _localStorage.RemoveItemAsync(StorageKeys.JWTTokenKeyName);
await _localStorage.RemoveItemAsync(StorageKeys.RefreshTokenKeyName);
var authState = Task.FromResult(new AuthenticationState(anonymous));
NotifyAuthenticationStateChanged(authState);
}
}
public record UserInfo(long Id = 0, string Name = null!, string Email = null!, string MobilePhone = null!, string Role = null!, string[] Permissions = null!);
}

View File

@@ -0,0 +1,40 @@
using Atomx.Common.Configuration;
using Microsoft.JSInterop;
namespace Atomx.Admin.Client.Utils
{
public interface ITokenProvider
{
Task<string?> GetTokenAsync();
Task<bool> IsTokenValidAsync();
}
public class ClientTokenProvider : ITokenProvider
{
private readonly IJSRuntime _jsRuntime;
public ClientTokenProvider(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task<string?> GetTokenAsync()
{
try
{
// 从localStorage或sessionStorage获取token
return await _jsRuntime.InvokeAsync<string>("localStorage.getItem", StorageKeys.JWTTokenKeyName);
}
catch
{
return null;
}
}
public async Task<bool> IsTokenValidAsync()
{
var token = await GetTokenAsync();
return !string.IsNullOrEmpty(token);
}
}
}