GET /api/public/home

GET /api/public/notices/{id}
SQLite / MySQL 双数据库只读连接
.env 与现有数据库配置兼容
公告 HTML 安全清洗
学校、班级、考试、科目、报名及系统通知聚合
This commit is contained in:
biss committed 2026-07-22 18:59:53 +08:00
1 parent 241fa8a6d7
commit 38e42ad734
23 files changed
+1144 -13

No files matched your search

@@ -0,0 +1,10 @@
using System.Text.Json.Nodes;
namespace Eis.Application.Public;
public interface IPublicQueryService
{
Task<JsonObject> GetHomeAsync(CancellationToken cancellationToken);
Task<JsonObject?> GetNoticeAsync(string id, CancellationToken cancellationToken);
}
@@ -0,0 +1,24 @@
namespace Eis.Application.Public;
public interface IPublicSiteConfiguration
{
PublicSiteBranding Branding { get; }
}
public sealed record PublicSiteBranding(
PublicOrganization Organization,
PublicSiteCopy SiteCopy);
public sealed record PublicOrganization(
string Name,
string Code,
string Phone,
string Address,
string Email);
public sealed record PublicSiteCopy(
string HeroEyebrow,
string HeroTitle,
string HeroHighlight,
string HeroDescription,
string FooterNotice);
@@ -0,0 +1,92 @@
using MySqlConnector;
namespace Eis.Infrastructure.Data;
public sealed class DatabaseOptions
{
private DatabaseOptions(string client, string? sqlitePath, string? mysqlConnectionString)
{
Client = client;
SqlitePath = sqlitePath;
MySqlConnectionString = mysqlConnectionString;
}
public string Client { get; }
public string? SqlitePath { get; }
public string? MySqlConnectionString { get; }
public static DatabaseOptions FromEnvironment(string applicationRoot, bool production)
{
var client = (Environment.GetEnvironmentVariable("DATABASE_CLIENT")
?? (production ? "mysql" : "sqlite")).Trim().ToLowerInvariant();
return client switch
{
"sqlite" => new DatabaseOptions(client, ResolveSqlitePath(applicationRoot), null),
"mysql" => new DatabaseOptions(client, null, BuildMySqlConnectionString()),
_ => throw new InvalidOperationException($"不支持的数据库类型:{client}")
};
}
private static string ResolveSqlitePath(string applicationRoot)
{
var configured = Environment.GetEnvironmentVariable("SQLITE_PATH");
var path = string.IsNullOrWhiteSpace(configured) ? Path.Combine("data", "exam.sqlite") : configured;
return Path.GetFullPath(Path.IsPathRooted(path) ? path : Path.Combine(applicationRoot, path));
}
private static string BuildMySqlConnectionString()
{
var connectionUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
if (!string.IsNullOrWhiteSpace(connectionUrl))
{
return BuildFromUrl(connectionUrl);
}
var host = Environment.GetEnvironmentVariable("MYSQL_HOST");
var user = Environment.GetEnvironmentVariable("MYSQL_USER");
var database = Environment.GetEnvironmentVariable("MYSQL_DATABASE");
if (string.IsNullOrWhiteSpace(host) || string.IsNullOrWhiteSpace(user) || string.IsNullOrWhiteSpace(database))
{
throw new InvalidOperationException("MySQL 配置不完整:请设置 DATABASE_URL,或 MYSQL_HOST、MYSQL_USER、MYSQL_DATABASE");
}
var builder = CreateMySqlBuilder();
builder.Server = host;
builder.Port = uint.TryParse(Environment.GetEnvironmentVariable("MYSQL_PORT"), out var port) ? port : 3306;
builder.UserID = user;
builder.Password = Environment.GetEnvironmentVariable("MYSQL_PASSWORD") ?? string.Empty;
builder.Database = database;
return builder.ConnectionString;
}
private static string BuildFromUrl(string connectionUrl)
{
if (!Uri.TryCreate(connectionUrl, UriKind.Absolute, out var uri) ||
!uri.Scheme.Equals("mysql", StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException("DATABASE_URL 必须是有效的 mysql:// 地址");
}
var credentials = uri.UserInfo.Split(':', 2);
var builder = CreateMySqlBuilder();
builder.Server = uri.Host;
builder.Port = uri.IsDefaultPort ? 3306u : checked((uint)uri.Port);
builder.UserID = Uri.UnescapeDataString(credentials[0]);
builder.Password = credentials.Length > 1 ? Uri.UnescapeDataString(credentials[1]) : string.Empty;
builder.Database = Uri.UnescapeDataString(uri.AbsolutePath.Trim('/'));
return builder.ConnectionString;
}
private static MySqlConnectionStringBuilder CreateMySqlBuilder() => new()
{
CharacterSet = "utf8mb4",
ConnectionTimeout = 10,
DefaultCommandTimeout = 30,
Pooling = true,
MaximumPoolSize = uint.TryParse(Environment.GetEnvironmentVariable("MYSQL_CONNECTION_LIMIT"), out var limit) ? limit : 10,
ConnectionReset = true
};
}
Loaded 3 of 23 files, more files were not shown because too many files have changed in this diff. Show more