GET /api/public/home
GET /api/public/notices/{id}
SQLite / MySQL 双数据库只读连接
.env 与现有数据库配置兼容
公告 HTML 安全清洗
学校、班级、考试、科目、报名及系统通知聚合
This commit is contained in:
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
Reference in new issue
Block a user