GET /api/public/home

GET /api/public/notices/{id}
SQLite / MySQL 双数据库只读连接
.env 与现有数据库配置兼容
公告 HTML 安全清洗
学校、班级、考试、科目、报名及系统通知聚合
This commit is contained in:
2026-07-22 18:59:53 +08:00 Unverified
parent 241fa8a6d7
commit 38e42ad734
23 changed files with 1144 additions and 13 deletions
@@ -0,0 +1,23 @@
namespace Eis.Web.Configuration;
public static class ApplicationPaths
{
public static string FindApplicationRoot()
{
foreach (var start in new[] { Directory.GetCurrentDirectory(), AppContext.BaseDirectory })
{
var directory = new DirectoryInfo(Path.GetFullPath(start));
while (directory is not null)
{
if (File.Exists(Path.Combine(directory.FullName, "Eis.slnx")) ||
File.Exists(Path.Combine(directory.FullName, "package.json")))
{
return directory.FullName;
}
directory = directory.Parent;
}
}
return Path.GetFullPath(Directory.GetCurrentDirectory());
}
}
@@ -0,0 +1,45 @@
namespace Eis.Web.Configuration;
public static class EnvironmentFile
{
public static void Load(string applicationRoot)
{
var path = Path.Combine(applicationRoot, ".env");
if (!File.Exists(path))
{
return;
}
foreach (var rawLine in File.ReadLines(path))
{
var line = rawLine.Trim();
if (line.Length == 0 || line.StartsWith('#'))
{
continue;
}
if (line.StartsWith("export ", StringComparison.Ordinal))
{
line = line[7..].TrimStart();
}
var separator = line.IndexOf('=');
if (separator <= 0)
{
continue;
}
var key = line[..separator].Trim();
if (Environment.GetEnvironmentVariable(key) is not null)
{
continue;
}
var value = line[(separator + 1)..].Trim();
if (value.Length >= 2 && ((value[0] == '"' && value[^1] == '"') || (value[0] == '\'' && value[^1] == '\'')))
{
value = value[1..^1];
}
Environment.SetEnvironmentVariable(key, value);
}
}
}
@@ -0,0 +1,28 @@
using Eis.Application.Public;
namespace Eis.Web.Configuration;
public sealed class PublicSiteConfiguration : IPublicSiteConfiguration
{
public PublicSiteConfiguration()
{
Branding = new PublicSiteBranding(
new PublicOrganization(
Read("PUBLIC_SITE_NAME", "考试服务平台"),
Read("PUBLIC_SITE_CODE", "EXAM-SERVICE"),
Read("PUBLIC_SITE_PHONE", string.Empty),
Read("PUBLIC_SITE_ADDRESS", string.Empty),
Read("PUBLIC_SITE_EMAIL", string.Empty)),
new PublicSiteCopy(
Read("PUBLIC_SITE_HERO_EYEBROW", "EXAMINATION SERVICE"),
Read("PUBLIC_SITE_HERO_TITLE", "一个报名号,"),
Read("PUBLIC_SITE_HERO_HIGHLIGHT", "贯穿每一次考试。"),
Read("PUBLIC_SITE_HERO_DESCRIPTION", "使用学校下发的报名号登录,完成密码更新和个人信息核验后,即可办理所有考试事项。"),
Read("PUBLIC_SITE_FOOTER_NOTICE", string.Empty)));
}
public PublicSiteBranding Branding { get; }
private static string Read(string name, string fallback) =>
Environment.GetEnvironmentVariable(name) ?? fallback;
}
+11
View File
@@ -1,8 +1,15 @@
using System.Net;
using Eis.Application.Public;
using Eis.Infrastructure;
using Eis.Infrastructure.Data;
using Eis.Infrastructure.Migration;
using Eis.Web.Configuration;
using Eis.Web.Frontend;
using Eis.Web.Legacy;
using Eis.Web.Public;
var applicationRoot = ApplicationPaths.FindApplicationRoot();
EnvironmentFile.Load(applicationRoot);
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options => options.AddServerHeader = false);
@@ -20,6 +27,8 @@ builder.Services.AddHttpClient<LegacyApiProxy>((services, client) =>
UseCookies = false
});
builder.Services.AddProblemDetails();
builder.Services.AddSingleton<IPublicSiteConfiguration, PublicSiteConfiguration>();
builder.Services.AddEisInfrastructure(DatabaseOptions.FromEnvironment(applicationRoot, builder.Environment.IsProduction()));
var app = builder.Build();
@@ -51,6 +60,8 @@ app.MapGet("/health/migration", async (LegacyApiProxy proxy, CancellationToken c
}, statusCode: statusCode);
});
app.MapNativePublicEndpoints();
string[] methods =
[
HttpMethods.Get,
+33
View File
@@ -0,0 +1,33 @@
using Eis.Application.Public;
namespace Eis.Web.Public;
public static class PublicEndpoints
{
public static IEndpointRouteBuilder MapNativePublicEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/api/public/home", async (
HttpContext context,
IPublicQueryService queries,
CancellationToken cancellationToken) =>
{
context.Response.Headers["X-EIS-Implementation"] = "aspnet-core";
return Results.Json(await queries.GetHomeAsync(cancellationToken));
});
endpoints.MapGet("/api/public/notices/{id}", async (
string id,
HttpContext context,
IPublicQueryService queries,
CancellationToken cancellationToken) =>
{
context.Response.Headers["X-EIS-Implementation"] = "aspnet-core";
var notice = await queries.GetNoticeAsync(id, cancellationToken);
return notice is null
? Results.Json(new { ok = false, message = "通知不存在或尚未发布" }, statusCode: StatusCodes.Status404NotFound)
: Results.Json(new { ok = true, notice });
});
return endpoints;
}
}