已完成管理后台第一批只读功能的 ASP.NET Core 10 迁移:

原生接口:context、dashboard、schools、school-organization、admins、exams
保持超级、校级、班级管理员的数据范围及权限隔离
其余管理写入、审批流和考务编排仍由 Node 转发
新增 ADMIN_NATIVE_READS_ENABLED=true 开关,并强制要求原生认证和共享 Redis
This commit is contained in:
2026-07-22 20:34:13 +08:00 Unverified
parent 4b0dae6d71
commit 712bd1a3a2
12 changed files with 894 additions and 5 deletions
+45
View File
@@ -422,6 +422,8 @@ try {
AUTH_NATIVE_ENABLED = 'true'
CANDIDATE_NATIVE_ENABLED = 'true'
CANDIDATE_NATIVE_ALLOW_MEMORY = 'true'
ADMIN_NATIVE_READS_ENABLED = 'true'
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
LegacyNode__Enabled = 'true'
LegacyNode__BaseUrl = $legacyBaseUrl
DATABASE_CLIENT = 'sqlite'
@@ -692,6 +694,48 @@ try {
if ($nativeLogin.ok -ne $true -or $nativeLogin.user.username -ne 'admin') {
throw 'Native authentication could not verify the existing Node PBKDF2 account'
}
foreach ($adminReadRoute in @('context', 'dashboard', 'schools', 'admins', 'exams')) {
$legacyAdminRead = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/$adminReadRoute" -WebSession $session
$nativeAdminRead = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/$adminReadRoute" -WebSession $nativeSession
if ($nativeAdminRead.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw "Admin read route '$adminReadRoute' did not use the native ASP.NET Core endpoint"
}
Assert-JsonEquivalent -Expected $legacyAdminRead.Content -Actual $nativeAdminRead.Content -Label "Admin read route '$adminReadRoute'"
}
$superSchoolOrganization = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/school-organization" -WebSession $nativeSession -SkipHttpErrorCheck
if ($superSchoolOrganization.StatusCode -ne 403 -or $superSchoolOrganization.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native school-organization route did not preserve the school-level authorization boundary'
}
$schoolLoginBody = @{ username = 'school_admin'; password = '12345678' } | ConvertTo-Json -Compress
$legacySchoolSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
$nativeSchoolSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
Invoke-RestMethod -Uri "$legacyBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $schoolLoginBody -WebSession $legacySchoolSession | Out-Null
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $schoolLoginBody -WebSession $nativeSchoolSession | Out-Null
foreach ($schoolAdminRoute in @('context', 'dashboard', 'school-organization', 'admins')) {
$legacySchoolRead = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/$schoolAdminRoute" -WebSession $legacySchoolSession
$nativeSchoolRead = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/$schoolAdminRoute" -WebSession $nativeSchoolSession
Assert-JsonEquivalent -Expected $legacySchoolRead.Content -Actual $nativeSchoolRead.Content -Label "School admin read route '$schoolAdminRoute'"
}
foreach ($superOnlyRoute in @('schools', 'exams')) {
$schoolForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/$superOnlyRoute" -WebSession $nativeSchoolSession -SkipHttpErrorCheck
if ($schoolForbidden.StatusCode -ne 403 -or $schoolForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw "Native admin route '$superOnlyRoute' did not preserve the super-admin boundary"
}
}
$classLoginBody = @{ username = 'class_admin'; password = '12345678' } | ConvertTo-Json -Compress
$legacyClassSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
$nativeClassSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
Invoke-RestMethod -Uri "$legacyBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $classLoginBody -WebSession $legacyClassSession | Out-Null
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $classLoginBody -WebSession $nativeClassSession | Out-Null
foreach ($classAdminRoute in @('context', 'dashboard')) {
$legacyClassRead = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/$classAdminRoute" -WebSession $legacyClassSession
$nativeClassRead = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/$classAdminRoute" -WebSession $nativeClassSession
Assert-JsonEquivalent -Expected $legacyClassRead.Content -Actual $nativeClassRead.Content -Label "Class admin read route '$classAdminRoute'"
}
$classAdminsForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admins" -WebSession $nativeClassSession -SkipHttpErrorCheck
if ($classAdminsForbidden.StatusCode -ne 403) {
throw 'Native admins route did not preserve the class-admin boundary'
}
$adminCandidateRoute = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/profile" -WebSession $nativeSession -SkipHttpErrorCheck
if ($adminCandidateRoute.StatusCode -ne 403) {
throw 'Native candidate API did not enforce the candidate role boundary'
@@ -774,6 +818,7 @@ try {
NativeCandidateWrites = 'passed'
NativeCandidateDocuments = 'passed'
NativeCandidateAdmissions = 'passed'
NativeAdminReads = 'passed'
} | Format-List
}
finally {