mirror of
https://github.com/Megghy/MegghysAPI.git
synced 2025-12-06 14:16:56 +08:00
Compare commits
3 Commits
1fa5e0c037
...
b2c648c3e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2c648c3e6 | ||
|
|
5863336e20 | ||
| 41767d1195 |
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
|
||||
<head>
|
||||
@@ -7,17 +7,17 @@
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["MegghysAPI.styles.css"]" />
|
||||
<link rel="stylesheet" href="_content/AntDesign/css/ant-design-blazor.css" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<HeadOutlet />
|
||||
|
||||
<script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js" type="text/javascript"></script>
|
||||
<loading-theme storage-name="theme"></loading-theme>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ConfigProvider>
|
||||
<Routes />
|
||||
<FluentDesignTheme StorageName="theme" />
|
||||
</ConfigProvider>
|
||||
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<FluentLayout>
|
||||
<FluentHeader>
|
||||
MegghysAPI
|
||||
</FluentHeader>
|
||||
<FluentStack Class="main" Orientation="Orientation.Horizontal" Width="100%">
|
||||
<NavMenu />
|
||||
<FluentBodyContent Class="body-content">
|
||||
<Layout class="app-layout">
|
||||
<Header class="app-header">
|
||||
<div class="app-header-title">MegghysAPI</div>
|
||||
</Header>
|
||||
<Layout>
|
||||
<Sider Collapsible="true"
|
||||
Collapsed="@collapsed"
|
||||
OnCollapse="HandleCollapse"
|
||||
Width="220"
|
||||
Class="app-sider">
|
||||
<NavMenu InlineCollapsed="@collapsed" />
|
||||
</Sider>
|
||||
<Layout>
|
||||
<Content class="app-content">
|
||||
<div class="content">
|
||||
@Body
|
||||
</div>
|
||||
</FluentBodyContent>
|
||||
</FluentStack>
|
||||
<FluentFooter>
|
||||
<a href="https://www.fluentui-blazor.net" target="_blank">Documentation and demos</a>
|
||||
<FluentSpacer />
|
||||
<a href="https://learn.microsoft.com/en-us/aspnet/core/blazor" target="_blank">About Blazor</a>
|
||||
</FluentFooter>
|
||||
</FluentLayout>
|
||||
</Content>
|
||||
<Footer class="app-footer">
|
||||
<a href="https://antblazor.com" target="_blank">Ant Design Blazor 文档</a>
|
||||
<span class="footer-spacer"></span>
|
||||
<a href="https://learn.microsoft.com/aspnet/core/blazor" target="_blank">About Blazor</a>
|
||||
</Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
||||
<div id="blazor-error-ui" data-nosnippet>
|
||||
An unhandled error has occurred.
|
||||
<a href="." class="reload">Reload</a>
|
||||
<span class="dismiss">🗙</span>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapsed;
|
||||
|
||||
private void HandleCollapse(bool isCollapsed)
|
||||
{
|
||||
collapsed = isCollapsed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,80 @@
|
||||
@rendermode InteractiveServer
|
||||
@rendermode InteractiveServer
|
||||
@implements IDisposable
|
||||
|
||||
<div class="navmenu">
|
||||
<input type="checkbox" title="Menu expand/collapse toggle" id="navmenu-toggle" class="navmenu-icon" />
|
||||
<label for="navmenu-toggle" class="navmenu-icon"><FluentIcon Value="@(new Icons.Regular.Size20.Navigation())" Color="Color.Fill" /></label>
|
||||
<nav class="sitenav" aria-labelledby="main-menu">
|
||||
<FluentNavMenu Id="main-menu" Collapsible="true" Width="250" Title="Navigation menu" @bind-Expanded="expanded" CustomToggle="true">
|
||||
<FluentNavLink Href="/" Match="NavLinkMatch.All" Icon="@(new Icons.Regular.Size20.Home())" IconColor="Color.Accent">Home</FluentNavLink>
|
||||
<FluentNavLink Href="pixiv" Icon="@(new Icons.Regular.Size20.NumberSymbolSquare())" IconColor="Color.Accent">Pixiv</FluentNavLink>
|
||||
<FluentNavLink Href="weather" Icon="@(new Icons.Regular.Size20.WeatherPartlyCloudyDay())" IconColor="Color.Accent">Weather</FluentNavLink>
|
||||
</FluentNavMenu>
|
||||
</nav>
|
||||
<FluentDesignTheme @bind-Mode="@Mode" @bind-OfficeColor="@OfficeColor" StorageName="theme" />
|
||||
</div>
|
||||
<Menu Mode="MenuMode.Inline"
|
||||
Theme="MenuTheme.Dark"
|
||||
InlineCollapsed="@InlineCollapsed"
|
||||
SelectedKeys="@selectedKeys"
|
||||
OnMenuItemClicked="HandleMenuClick"
|
||||
Class="app-menu">
|
||||
<MenuItem Key="/">
|
||||
<Icon Type="IconType.OutlineHome" />
|
||||
<span>Home</span>
|
||||
</MenuItem>
|
||||
<MenuItem Key="pixiv">
|
||||
<Icon Type="IconType.OutlinePicture" />
|
||||
<span>Pixiv</span>
|
||||
</MenuItem>
|
||||
<MenuItem Key="weather">
|
||||
<Icon Type="IconType.OutlineCloud" />
|
||||
<span>Weather</span>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
@code {
|
||||
private bool expanded = true;
|
||||
public DesignThemeModes Mode { get; set; }
|
||||
public OfficeColor? OfficeColor { get; set; }
|
||||
[Parameter]
|
||||
public bool InlineCollapsed { get; set; }
|
||||
|
||||
private string[] selectedKeys = ["/"];
|
||||
|
||||
[Inject]
|
||||
private NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
UpdateSelectedKeys(NavigationManager.Uri);
|
||||
}
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
UpdateSelectedKeys(NavigationManager.Uri);
|
||||
NavigationManager.LocationChanged += HandleLocationChanged;
|
||||
}
|
||||
|
||||
private void HandleLocationChanged(object? sender, LocationChangedEventArgs e)
|
||||
{
|
||||
UpdateSelectedKeys(e.Location);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void UpdateSelectedKeys(string uri)
|
||||
{
|
||||
var relative = NavigationManager.ToBaseRelativePath(uri);
|
||||
if (string.IsNullOrEmpty(relative))
|
||||
{
|
||||
selectedKeys = ["/"];
|
||||
}
|
||||
else
|
||||
{
|
||||
var key = relative.Split('/', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? string.Empty;
|
||||
selectedKeys = [key];
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleMenuClick(MenuItem menuItem)
|
||||
{
|
||||
if (menuItem.Key is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
selectedKeys = [menuItem.Key];
|
||||
var target = menuItem.Key == "/" ? "/" : $"/{menuItem.Key}";
|
||||
NavigationManager.NavigateTo(target);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
NavigationManager.LocationChanged -= HandleLocationChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
@page "/counter"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Counter</PageTitle>
|
||||
|
||||
<h1>Counter</h1>
|
||||
|
||||
<div role="status" style="padding-bottom: 1em;">
|
||||
Current count: <FluentBadge Appearance="Appearance.Neutral">@currentCount</FluentBadge>
|
||||
</div>
|
||||
|
||||
<FluentButton Appearance="Appearance.Accent" @onclick="IncrementCount">Click me</FluentButton>
|
||||
|
||||
@code {
|
||||
private int currentCount = 0;
|
||||
|
||||
private void IncrementCount()
|
||||
{
|
||||
currentCount++;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
@page "/pixiv"
|
||||
@page "/pixiv"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<h3>Pixiv</h3>
|
||||
|
||||
<FluentButton OnClick="RandomGet">
|
||||
随机获取
|
||||
</FluentButton>
|
||||
<FluentDivider />
|
||||
<br/>
|
||||
<FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center">
|
||||
<Space Direction="SpaceDirection.Horizontal" Size="@("16")" Align="SpaceAlign.Center">
|
||||
<Button Type="ButtonType.Primary" OnClick="RandomGet">随机获取</Button>
|
||||
</Space>
|
||||
<Divider />
|
||||
<Space Direction="SpaceDirection.Vertical" Size="@("16")" Align="SpaceAlign.Center">
|
||||
@foreach (var (index, img) in CurrentImgs.S3URL.Index())
|
||||
{
|
||||
@if(index == 0)
|
||||
@@ -20,7 +19,7 @@
|
||||
<img src="@img.Large" loading="lazy" referrerpolicy="no-referrer" />
|
||||
}
|
||||
}
|
||||
</FluentStack>
|
||||
</Space>
|
||||
|
||||
@code {
|
||||
public Modules.PixivFavoriteDownloader.Pixiv.PixivImgInfo CurrentImgs;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
@page "/weather"
|
||||
@attribute [StreamRendering]
|
||||
|
||||
<PageTitle>Weather</PageTitle>
|
||||
|
||||
<h1>Weather</h1>
|
||||
|
||||
<p>This component demonstrates showing data.</p>
|
||||
|
||||
<!-- This page is rendered in SSR mode, so the FluentDataGrid component does not offer any interactivity (like sorting). -->
|
||||
<FluentDataGrid Id="weathergrid" Items="@forecasts" GridTemplateColumns="1fr 1fr 1fr 2fr" Loading="@(forecasts == null)" Style="height:204px;" TGridItem="WeatherForecast">
|
||||
<PropertyColumn Title="Date" Property="@(c => c!.Date)" Align="Align.Start"/>
|
||||
<PropertyColumn Title="Temp. (C)" Property="@(c => c!.TemperatureC)" Align="Align.Center"/>
|
||||
<PropertyColumn Title="Temp. (F)" Property="@(c => c!.TemperatureF)" Align="Align.Center"/>
|
||||
<PropertyColumn Title="Summary" Property="@(c => c!.Summary)" Align="Align.End"/>
|
||||
</FluentDataGrid>
|
||||
|
||||
@code {
|
||||
private IQueryable<WeatherForecast>? forecasts;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Simulate asynchronous loading to demonstrate streaming rendering
|
||||
await Task.Delay(500);
|
||||
|
||||
var startDate = DateOnly.FromDateTime(DateTime.Now);
|
||||
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
|
||||
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = startDate.AddDays(index),
|
||||
TemperatureC = Random.Shared.Next(-20, 55),
|
||||
Summary = summaries[Random.Shared.Next(summaries.Length)]
|
||||
}).AsQueryable();
|
||||
}
|
||||
|
||||
private class WeatherForecast
|
||||
{
|
||||
public DateOnly Date { get; set; }
|
||||
public int TemperatureC { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.FluentUI.AspNetCore.Components
|
||||
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||
@using AntDesign
|
||||
@using Microsoft.JSInterop
|
||||
@using MegghysAPI
|
||||
@using MegghysAPI.Components
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Unicode;
|
||||
|
||||
@@ -63,5 +63,11 @@ namespace MegghysAPI
|
||||
public string MinIOSecretKey { get; set; } = "Nko5azOSUiYgOUeLsj8hLxGz4cKC8XOcH0VS7lWq";
|
||||
public string MinIORegion { get; set; } = "cn-main";
|
||||
public string MinIOBucket { get; set; } = "general";
|
||||
|
||||
// DNS解析配置
|
||||
public string DnsResolverAccountId { get; set; } = "720341";
|
||||
public string DnsResolverAkId { get; set; } = "720341_30798028364391424";
|
||||
public string DnsResolverAkSecret { get; set; } = "0739b7584ab54c1b9585f10594af0cd0";
|
||||
public string DnsResolverEndpoint { get; set; } = "https://223.5.5.5/resolve";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,109 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MegghysAPI.Controllers
|
||||
{
|
||||
public class DnsRecord
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public int Type { get; set; }
|
||||
|
||||
[JsonPropertyName("TTL")]
|
||||
public int Ttl { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public string Data { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class DnsResponse
|
||||
{
|
||||
public class DnsQuestion
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public int type { get; set; }
|
||||
}
|
||||
[JsonPropertyName("Status")]
|
||||
public int Status { get; set; }
|
||||
|
||||
[JsonPropertyName("TC")]
|
||||
public bool Tc { get; set; }
|
||||
|
||||
[JsonPropertyName("RD")]
|
||||
public bool Rd { get; set; }
|
||||
|
||||
[JsonPropertyName("RA")]
|
||||
public bool Ra { get; set; }
|
||||
|
||||
[JsonPropertyName("AD")]
|
||||
public bool Ad { get; set; }
|
||||
|
||||
[JsonPropertyName("CD")]
|
||||
public bool Cd { get; set; }
|
||||
|
||||
[JsonPropertyName("Question")]
|
||||
public DnsQuestion Question { get; set; }
|
||||
|
||||
[JsonPropertyName("Answer")]
|
||||
public List<DnsRecord>? Answer { get; set; }
|
||||
}
|
||||
|
||||
[Route("api/public")]
|
||||
[ApiController]
|
||||
public class PublicController : MControllerBase
|
||||
{
|
||||
private static readonly HttpClient _httpClient = new();
|
||||
|
||||
private static string CalculateKey(string accountId, string akSecret, long timestamp, string qname, string akId)
|
||||
{
|
||||
var input = $"{accountId}{akSecret}{timestamp}{qname}{akId}";
|
||||
using var sha256 = SHA256.Create();
|
||||
var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
|
||||
}
|
||||
|
||||
[HttpGet("resolve-dns")]
|
||||
public async Task<IActionResult> ResolveDns([FromQuery] string domain, [FromQuery] string recordType = "A")
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = Config.Instance;
|
||||
var accountId = config.DnsResolverAccountId;
|
||||
var akId = config.DnsResolverAkId;
|
||||
var akSecret = config.DnsResolverAkSecret;
|
||||
var endpoint = config.DnsResolverEndpoint;
|
||||
|
||||
if (string.IsNullOrEmpty(accountId) || string.IsNullOrEmpty(akId) || string.IsNullOrEmpty(akSecret) || string.IsNullOrEmpty(endpoint))
|
||||
{
|
||||
return new BadRequestObjectResult("DNS解析配置不完整。");
|
||||
}
|
||||
|
||||
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
var key = CalculateKey(accountId, akSecret, timestamp, domain, akId);
|
||||
|
||||
var url = $"{endpoint}?name={Uri.EscapeDataString(domain)}&type={recordType}&uid={accountId}&ak={akId}&key={key}&ts={timestamp}";
|
||||
|
||||
Logs.Info($"正在向 {url} 发起DNS请求");
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
var dnsResponse = JsonSerializer.Deserialize<DnsResponse>(content);
|
||||
|
||||
return new OkObjectResult(dnsResponse);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.Error($"解析DNS记录时出错: {ex.Message}");
|
||||
return new StatusCodeResult(500);
|
||||
}
|
||||
}
|
||||
[HttpGet("header")]
|
||||
public IActionResult Header(bool? order = false)
|
||||
{
|
||||
|
||||
1
DB.cs
1
DB.cs
@@ -6,7 +6,6 @@ namespace MegghysAPI
|
||||
public static class DB
|
||||
{
|
||||
public static IFreeSql SQL { get; private set; }
|
||||
[AutoInit(Order = 0)]
|
||||
internal static void InitDB()
|
||||
{
|
||||
SQL = new FreeSqlBuilder()
|
||||
|
||||
@@ -7,5 +7,9 @@
|
||||
"MinIOAccessKey": "RBzbElm21lf7sy7wK7wG",
|
||||
"MinIOSecretKey": "Nko5azOSUiYgOUeLsj8hLxGz4cKC8XOcH0VS7lWq",
|
||||
"MinIORegion": "cn-main",
|
||||
"MinIOBucket": "general"
|
||||
"MinIOBucket": "general",
|
||||
"DnsResolverAccountId": "720341",
|
||||
"DnsResolverAkId": "720341_30798028364391424",
|
||||
"DnsResolverAkSecret": "0739b7584ab54c1b9585f10594af0cd0",
|
||||
"DnsResolverEndpoint": "https://223.5.5.5/resolve"
|
||||
}
|
||||
@@ -2,17 +2,16 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<Nullable>annotations</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FreeSql.Extensions.JsonMap" Version="3.5.104" />
|
||||
<PackageReference Include="FreeSql.Provider.PostgreSQL" Version="3.5.104" />
|
||||
<PackageReference Include="Masuit.Tools.Core" Version="2025.1.0" />
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.11.3" />
|
||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.11.3" />
|
||||
<PackageReference Include="Minio" Version="6.0.4" />
|
||||
<PackageReference Include="FreeSql.Extensions.JsonMap" Version="3.5.213" />
|
||||
<PackageReference Include="FreeSql.Provider.PostgreSQL" Version="3.5.213" />
|
||||
<PackageReference Include="Masuit.Tools.Core" Version="2025.5.1" />
|
||||
<PackageReference Include="Minio" Version="6.0.5" />
|
||||
<PackageReference Include="AntDesign" Version="1.4.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Web;
|
||||
using FreeSql.DataAnnotations;
|
||||
using MegghysAPI.Attributes;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static MegghysAPI.Modules.PixivFavoriteDownloader.Pixiv;
|
||||
|
||||
namespace MegghysAPI.Modules
|
||||
@@ -57,6 +58,10 @@ namespace MegghysAPI.Modules
|
||||
.ExecuteAffrows();
|
||||
Logs.Success($"[{Favorites.Count}] 发现新增收藏: {Favorites.Last().Title}, 类型: {Favorites.Last().Restrict}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return; // 不存在更新的了
|
||||
}
|
||||
});
|
||||
var next = (string)json["next_url"];
|
||||
if (next is null)
|
||||
@@ -180,19 +185,18 @@ namespace MegghysAPI.Modules
|
||||
public long Id { get; set; }
|
||||
public PixivImgType Type { get; set; }
|
||||
public string Title { get; set; }
|
||||
[JsonMap]
|
||||
public PixivRestrictInfo Restrict { get; set; }
|
||||
[Column(DbType = "text")]
|
||||
public string Description { get; set; }
|
||||
[JsonMap]
|
||||
public PixivAuthorInfo Author { get; set; }
|
||||
public bool R18 => Restrict != PixivRestrictInfo.Normal;
|
||||
[JsonMap]
|
||||
[JsonMap, Column(MapType = typeof(JArray))]
|
||||
public List<PixivTagInfo> Tags { get; set; } = [];
|
||||
[JsonMap]
|
||||
[JsonMap, Column(MapType = typeof(JArray))]
|
||||
public List<PixivURLInfo> URL { get; set; } = [];
|
||||
|
||||
[JsonMap]
|
||||
[JsonMap, Column(MapType = typeof(JArray))]
|
||||
public List<PixivURLInfo> S3URL { get; set; } = [];
|
||||
public int Width { get; set; }
|
||||
public int Height { get; set; }
|
||||
|
||||
58
Program.cs
58
Program.cs
@@ -2,14 +2,14 @@ using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using MegghysAPI.Attributes;
|
||||
using MegghysAPI.Components;
|
||||
using Microsoft.FluentUI.AspNetCore.Components;
|
||||
using AntDesign;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.Services.AddFluentUIComponents();
|
||||
builder.Services.AddAntDesign();
|
||||
builder.Services.AddControllers();
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -36,36 +36,48 @@ app.UseAntiforgery();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> AutoInitAttribute <EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
// <EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>ǰAssembly
|
||||
// 加载所有有 AutoInitAttribute 的类
|
||||
// 获取当前Assembly
|
||||
var inits = new List<MethodInfo>();
|
||||
Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.ForEach(t => t.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
|
||||
.Where(m => m.GetCustomAttribute<AutoInitAttribute>() is { }).ForEach(m => inits.Add(m)));
|
||||
inits = [.. inits.OrderBy(m => m.GetCustomAttribute<AutoInitAttribute>().Order)];
|
||||
inits.ForEach(m =>
|
||||
foreach (var type in Assembly.GetExecutingAssembly().GetTypes())
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
var attr = m.GetCustomAttribute<AutoInitAttribute>();
|
||||
if (attr.LogMessage is not null)
|
||||
Logs.Info(attr.LogMessage);
|
||||
if (attr.Async)
|
||||
foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
|
||||
.Where(m => m.GetCustomAttribute<AutoInitAttribute>() is not null))
|
||||
{
|
||||
Task.Run(() =>
|
||||
inits.Add(method);
|
||||
}
|
||||
}
|
||||
|
||||
DB.InitDB();
|
||||
|
||||
inits = inits
|
||||
.OrderBy(m => m.GetCustomAttribute<AutoInitAttribute>()?.Order ?? 0)
|
||||
.ToList();
|
||||
|
||||
foreach (var method in inits)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var attr = method.GetCustomAttribute<AutoInitAttribute>();
|
||||
if (attr?.LogMessage is { } message)
|
||||
{
|
||||
m.Invoke(null, null);
|
||||
Logs.Info(message);
|
||||
}
|
||||
|
||||
if (attr?.Async is true)
|
||||
{
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
method.Invoke(null, null);
|
||||
attr.PostInit?.Invoke();
|
||||
Logs.Info($"[{sw.ElapsedMilliseconds} ms] Async <{m.DeclaringType.Name}.{m.Name}> => Inited.");
|
||||
Logs.Info($"[{sw.ElapsedMilliseconds} ms] Async <{method.DeclaringType?.Name}.{method.Name}> => Inited.");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
m.Invoke(null, null);
|
||||
attr.PostInit?.Invoke();
|
||||
Logs.Info($"[{sw.ElapsedMilliseconds} ms] <{m.DeclaringType.Name}.{m.Name}> => Inited.");
|
||||
method.Invoke(null, null);
|
||||
attr?.PostInit?.Invoke();
|
||||
Logs.Info($"[{sw.ElapsedMilliseconds} ms] <{method.DeclaringType?.Name}.{method.Name}> => Inited.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.Run();
|
||||
|
||||
217
wwwroot/app.css
217
wwwroot/app.css
@@ -1,80 +1,71 @@
|
||||
@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css';
|
||||
|
||||
body {
|
||||
--body-font: "Segoe UI Variable", "Segoe UI", sans-serif;
|
||||
font-family: var(--body-font);
|
||||
font-size: var(--type-ramp-base-font-size);
|
||||
line-height: var(--type-ramp-base-line-height);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
font-family: var(--body-font);
|
||||
font-size: var(--type-ramp-base-font-size);
|
||||
line-height: var(--type-ramp-base-line-height);
|
||||
font-weight: var(--font-weight);
|
||||
color: var(--neutral-foreground-rest);
|
||||
background: var(--neutral-fill-layer-rest);
|
||||
min-height: 100vh;
|
||||
font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
background: #f0f2f5;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.navmenu-icon {
|
||||
display: none;
|
||||
.app-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: calc(100dvh - 86px);
|
||||
color: var(--neutral-foreground-rest);
|
||||
align-items: stretch !important;
|
||||
}
|
||||
|
||||
.body-content {
|
||||
align-self: stretch;
|
||||
height: calc(100dvh - 86px) !important;
|
||||
.app-header {
|
||||
background: #001529;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 24px;
|
||||
height: 64px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.app-header-title {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.app-sider {
|
||||
background: #001529;
|
||||
min-height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.app-menu {
|
||||
height: 100%;
|
||||
border-inline-end: none !important;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
padding: 24px;
|
||||
background: #fff;
|
||||
min-height: calc(100vh - 64px - 70px);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0.5rem 1.5rem;
|
||||
align-self: stretch !important;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.manage {
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--neutral-layer-4);
|
||||
color: var(--neutral-foreground-rest);
|
||||
.app-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 10px;
|
||||
padding: 16px 24px;
|
||||
background: #fff;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: var(--neutral-foreground-rest);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
footer a:focus {
|
||||
outline: 1px dashed;
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.alert {
|
||||
border: 1px dashed var(--accent-fill-rest);
|
||||
padding: 5px;
|
||||
.footer-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.weather-table {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
background: #fffbe6;
|
||||
border: 1px solid #ffe58f;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
@@ -83,121 +74,43 @@ footer {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
border: 1px solid #ff4d4f;
|
||||
padding: 16px 24px;
|
||||
border-radius: 4px;
|
||||
background: #fff2f0;
|
||||
color: #a8071a;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::before {
|
||||
content: "An error has occurred. "
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
margin: 20vh auto 1rem auto;
|
||||
.blazor-error-boundary::before {
|
||||
content: "An error has occurred. ";
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.header-gutters {
|
||||
margin: 0.5rem 3rem 0.5rem 1.5rem !important;
|
||||
@media (max-width: 768px) {
|
||||
.app-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
[dir="rtl"] .header-gutters {
|
||||
margin: 0.5rem 1.5rem 0.5rem 3rem !important;
|
||||
.app-footer {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex-direction: column !important;
|
||||
row-gap: 0 !important;
|
||||
}
|
||||
|
||||
nav.sitenav {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#main-menu {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
#main-menu > div:first-child:is(.expander) {
|
||||
.footer-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navmenu {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#navmenu-toggle {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
#navmenu-toggle ~ nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#navmenu-toggle:checked ~ nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.navmenu-icon {
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: unset;
|
||||
right: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] .navmenu-icon {
|
||||
left: 20px;
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user