已完成本轮迁移:管理后台、审批流和考务编排的业务 API 已全部由 ASP.NET Core 10 原生处理。

主要完成:
管理端招生工作台、指标资格、招生学校账号。
招生计划审核、分数优先多志愿投档、退档监督。
逐轮录取公示、报到审批、补录轮次推进、最低录取线公示。
成绩预览导入与管理员账号禁止物理删除。
新增迁移开关及健康状态:[MIGRATION.md (line 57)](C:\\Users\\BI\\Documents\\EIS-dotnet\\MIGRATION.md:57)
核心实现:[AdminAdmissionService.cs (line 10)](C:\\Users\\BI\\Documents\\EIS-dotnet\\src\\Eis.Infrastructure\\Administration\\AdminAdmissionService.cs:10)
原生路由:[NativeAdminReadEndpoints.cs (line 209)](C:\\Users\\BI\\Documents\\EIS-dotnet\\src\\Eis.Web\\Administration\\NativeAdminReadEndpoints.cs:209)
This commit is contained in:
biss committed 2026-07-23 13:08:17 +08:00
1 parent d47c614b53
commit 4df6303153
14 files changed
+1810 -13

No files matched your search

@@ -0,0 +1,20 @@
using System.Text.Json.Nodes;
namespace Eis.Application.Administration;
public interface IAdminAdmissionService
{
Task<AdminEndpointResult> GetAsync(string sessionToken, CancellationToken cancellationToken);
Task<AdminEndpointResult> GetQualificationsAsync(string sessionToken, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveQualificationAsync(string sessionToken, string examId, string userId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveQualificationsAsync(string sessionToken, string examId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> CreateAccountAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> UpdateAccountAsync(string sessionToken, string accountId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> ResetAccountPasswordAsync(string sessionToken, string accountId, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveSettingAsync(string sessionToken, string examId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> SavePlanAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> ReviewPlanAsync(string sessionToken, string planId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> RunActionAsync(string sessionToken, string examId, string action, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> ReviewReportingAsync(string sessionToken, string recordId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> ReviewWithdrawalAsync(string sessionToken, string placementId, JsonObject body, CancellationToken cancellationToken);
}
@@ -7,6 +7,7 @@ public interface IAdminResultService
Task<AdminEndpointResult> GetAsync(string sessionToken, string examId, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveBulkAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> ImportAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveFeatureBulkAsync(string sessionToken, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> SaveFeatureAsync(string sessionToken, string registrationId, JsonObject body, CancellationToken cancellationToken);
Task<AdminEndpointResult> ReviewAppealAsync(string sessionToken, string resultId, JsonObject body, CancellationToken cancellationToken);
@@ -0,0 +1,133 @@
using System.Data.Common;
using Eis.Infrastructure.Candidate;
using Eis.Infrastructure.Data;
namespace Eis.Infrastructure.Administration;
internal sealed class AdminAdmissionRepository(IRelationalConnectionFactory connectionFactory)
{
public Task SaveRecordsAsync(
IReadOnlyList<CandidateAdmissionRecord> records,
AdminAuditEntry audit,
CancellationToken cancellationToken) =>
TransactionAsync(async (connection, transaction) =>
{
foreach (var record in records)
{
await ExecuteAsync(connection, transaction, "DELETE FROM admission_records WHERE id = @id",
[new("@id", record.Id)], cancellationToken);
await ExecuteAsync(connection, transaction,
"""
INSERT INTO admission_records (
id, kind, exam_id, user_id, school_id, status, payload_json, created_at, updated_at
) VALUES (
@id, @kind, @examId, @userId, @schoolId, @status, @payload, @createdAt, @updatedAt
)
""",
[
new("@id", record.Id), new("@kind", record.Kind), new("@examId", record.ExamId),
new("@userId", record.UserId), new("@schoolId", record.SchoolId),
new("@status", record.Status), new("@payload", record.Payload.ToJsonString()),
new("@createdAt", record.CreatedAt), new("@updatedAt", record.UpdatedAt)
], cancellationToken);
}
await InsertAuditAsync(connection, transaction, audit, cancellationToken);
}, cancellationToken);
public Task CreateAccountAsync(
AdminUser account,
string passwordHash,
AdminAuditEntry audit,
CancellationToken cancellationToken) =>
TransactionAsync(async (connection, transaction) =>
{
await ExecuteAsync(connection, transaction,
"""
INSERT INTO users (
id, username, password_hash, role, admin_level, school_id, class_id,
display_name, active, must_change_password, created_at
) VALUES (
@id, @username, @passwordHash, 'admission_school', NULL, @schoolId, NULL,
@displayName, 1, 0, @createdAt
)
""",
[
new("@id", account.Id), new("@username", account.Username), new("@passwordHash", passwordHash),
new("@schoolId", account.SchoolId), new("@displayName", account.DisplayName),
new("@createdAt", account.CreatedAt)
], cancellationToken);
await InsertAuditAsync(connection, transaction, audit, cancellationToken);
}, cancellationToken);
public Task UpdateAccountAsync(
string accountId,
string displayName,
bool active,
string? passwordHash,
AdminAuditEntry audit,
CancellationToken cancellationToken) =>
TransactionAsync(async (connection, transaction) =>
{
await ExecuteAsync(connection, transaction,
passwordHash is null
? "UPDATE users SET display_name = @displayName, active = @active WHERE id = @id"
: "UPDATE users SET display_name = @displayName, active = @active, password_hash = @passwordHash WHERE id = @id",
[
new("@displayName", displayName), new("@active", active ? 1 : 0),
new("@passwordHash", passwordHash), new("@id", accountId)
], cancellationToken);
await InsertAuditAsync(connection, transaction, audit, cancellationToken);
}, cancellationToken);
private async Task TransactionAsync(
Func<DbConnection, DbTransaction, Task> operation,
CancellationToken cancellationToken)
{
await using var connection = await connectionFactory.OpenAsync(cancellationToken);
await using var transaction = await connection.BeginTransactionAsync(cancellationToken);
try
{
await operation(connection, transaction);
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
private static Task InsertAuditAsync(
DbConnection connection,
DbTransaction transaction,
AdminAuditEntry audit,
CancellationToken cancellationToken) =>
ExecuteAsync(connection, transaction,
"INSERT INTO audit_logs (id, actor_id, action, detail, created_at) VALUES (@id, @actorId, @action, @detail, @createdAt)",
[
new("@id", audit.Id), new("@actorId", audit.ActorId), new("@action", audit.Action),
new("@detail", audit.Detail), new("@createdAt", audit.CreatedAt)
], cancellationToken);
private static async Task ExecuteAsync(
DbConnection connection,
DbTransaction transaction,
string sql,
IReadOnlyList<SqlValue> values,
CancellationToken cancellationToken)
{
await using var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = sql;
foreach (var value in values)
{
var parameter = command.CreateParameter();
parameter.ParameterName = value.Name;
parameter.Value = value.Value ?? DBNull.Value;
command.Parameters.Add(parameter);
}
await command.ExecuteNonQueryAsync(cancellationToken);
}
private sealed record SqlValue(string Name, object? Value);
}
Loaded 3 of 14 files, more files were not shown because too many files have changed in this diff. Show more