已完成管理后台第二批 ASP.NET Core 10 迁移:
学校创建、修改 班级创建、修改 管理员创建、修改 管理员密码重置 自主注册开关 写入与审计日志保持同一事务 停用或重置管理员后自动清除其会话
This commit is contained in:
1 parent
712bd1a3a2
commit
875e59b6ce
12 files changed
+884
-19
No files matched your search
@@ -27,6 +27,8 @@ AUTH_NATIVE_ENABLED=false
|
||||
CANDIDATE_NATIVE_ENABLED=false
|
||||
# 第一批管理端只读接口切换;必须与 AUTH_NATIVE_ENABLED=true 及共享 Redis 同时使用。
|
||||
ADMIN_NATIVE_READS_ENABLED=false
|
||||
# 学校、班级、管理员维护及自主注册开关切换;同样要求原生认证和共享 Redis。
|
||||
ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED=false
|
||||
|
||||
# 仅在首次创建空数据库时使用。部署前务必修改初始密码。
|
||||
INITIAL_ADMIN_USERNAME=admin
|
||||
|
||||
+6
-3
@@ -12,7 +12,7 @@
|
||||
- [x] 招生公示与 HMAC 文书验真公开接口
|
||||
- [x] 登录、自主注册、Session 与 TOTP(兼容开关默认关闭)
|
||||
- [x] 考生业务
|
||||
- [ ] 管理后台、审批流和考务编排(第一批管理端只读接口已原生化)
|
||||
- [ ] 管理后台、审批流和考务编排(管理端读取与组织维护已部分原生化)
|
||||
- [x] 考生志愿填报与招生录取查询
|
||||
- [ ] Excel、文书和缓存
|
||||
- [ ] 容器入口切换及 Node.js 后端移除
|
||||
@@ -54,14 +54,17 @@ $env:AUTH_NATIVE_ENABLED = 'true'
|
||||
$env:CANDIDATE_NATIVE_ENABLED = 'true'
|
||||
```
|
||||
|
||||
管理后台第一批只读接口(管理上下文、仪表盘、学校、学校组织、管理员和考试列表)已经原生化,并保留超级、校级、班级管理员的权限与数据作用域。其余管理端写入、审批流和考务编排接口仍转发给 Node,因此该开关同样要求原生认证和共享 Redis:
|
||||
管理后台第一批只读接口(管理上下文、仪表盘、学校、学校组织、管理员和考试列表)已经原生化,并保留超级、校级、班级管理员的权限与数据作用域。第二批覆盖学校、班级和管理员的创建与维护、管理员密码重置及自主注册开关;更新操作与审计日志在同一事务中提交,停用或重置管理员会同步失效其会话。
|
||||
|
||||
其余审批流和考务编排接口仍转发给 Node,因此两个管理端开关都要求原生认证和共享 Redis;组织维护开关还必须与只读开关一起启用:
|
||||
|
||||
```powershell
|
||||
$env:AUTH_NATIVE_ENABLED = 'true'
|
||||
$env:ADMIN_NATIVE_READS_ENABLED = 'true'
|
||||
$env:ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED = 'true'
|
||||
```
|
||||
|
||||
`GET /health/migration` 的 `administration.nativeReadsEnabled` 和 `administration.routes` 会报告这一批端点是否已切换。
|
||||
`GET /health/migration` 的 `administration.nativeReadsEnabled`、`administration.nativeOrganizationWritesEnabled` 和 `administration.nativeRoutes` 会报告这些端点是否已切换。
|
||||
|
||||
完整的宿主、静态资源、JSON 转发和 Session Cookie 冒烟测试:
|
||||
|
||||
|
||||
@@ -423,6 +423,7 @@ try {
|
||||
CANDIDATE_NATIVE_ENABLED = 'true'
|
||||
CANDIDATE_NATIVE_ALLOW_MEMORY = 'true'
|
||||
ADMIN_NATIVE_READS_ENABLED = 'true'
|
||||
ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED = 'true'
|
||||
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
|
||||
LegacyNode__Enabled = 'true'
|
||||
LegacyNode__BaseUrl = $legacyBaseUrl
|
||||
@@ -736,6 +737,120 @@ try {
|
||||
if ($classAdminsForbidden.StatusCode -ne 403) {
|
||||
throw 'Native admins route did not preserve the class-admin boundary'
|
||||
}
|
||||
|
||||
$schoolCreateBody = @{
|
||||
name = '原生迁移测试学校'
|
||||
code = 'NATIVE_SMOKE'
|
||||
address = '迁移测试路 1 号'
|
||||
isSourceSchool = $true
|
||||
isAdmissionSchool = $true
|
||||
} | ConvertTo-Json -Compress
|
||||
$schoolCreateResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/schools" -Method Post -ContentType 'application/json' -Body $schoolCreateBody -WebSession $nativeSession
|
||||
if ($schoolCreateResponse.StatusCode -ne 201 -or $schoolCreateResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native school creation did not use the ASP.NET Core endpoint'
|
||||
}
|
||||
$createdSchool = ($schoolCreateResponse.Content | ConvertFrom-Json).school
|
||||
if ($createdSchool.code -ne 'NATIVE_SMOKE' -or $createdSchool.active -ne $true) {
|
||||
throw 'Native school creation returned an unexpected projection'
|
||||
}
|
||||
$duplicateSchool = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/schools" -Method Post -ContentType 'application/json' -Body $schoolCreateBody -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($duplicateSchool.StatusCode -ne 409) {
|
||||
throw 'Native school creation did not reject a duplicate school code'
|
||||
}
|
||||
$schoolCreateForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/schools" -Method Post -ContentType 'application/json' -Body $schoolCreateBody -WebSession $nativeSchoolSession -SkipHttpErrorCheck
|
||||
if ($schoolCreateForbidden.StatusCode -ne 403 -or $schoolCreateForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native school creation did not preserve the super-admin boundary'
|
||||
}
|
||||
|
||||
$schoolPatchBody = @{ address = '迁移测试路 2 号'; isAdmissionSchool = $false } | ConvertTo-Json -Compress
|
||||
$schoolPatch = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/schools/$($createdSchool.id)" -Method Patch -ContentType 'application/json' -Body $schoolPatchBody -WebSession $nativeSession
|
||||
if ($schoolPatch.school.address -ne '迁移测试路 2 号' -or $schoolPatch.school.isAdmissionSchool -ne $false) {
|
||||
throw 'Native school update did not persist the requested fields'
|
||||
}
|
||||
|
||||
$newSchoolAdminBody = @{
|
||||
username = 'native_school_writer'
|
||||
password = '12345678'
|
||||
displayName = '原生校级管理员'
|
||||
adminLevel = 'school'
|
||||
schoolId = $createdSchool.id
|
||||
} | ConvertTo-Json -Compress
|
||||
$newSchoolAdminResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/admins" -Method Post -ContentType 'application/json' -Body $newSchoolAdminBody -WebSession $nativeSession
|
||||
if ($newSchoolAdminResponse.StatusCode -ne 201 -or $newSchoolAdminResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native administrator creation did not use the ASP.NET Core endpoint'
|
||||
}
|
||||
$createdSchoolAdmin = ($newSchoolAdminResponse.Content | ConvertFrom-Json).admin
|
||||
|
||||
$newSchoolAdminSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$newSchoolAdminLoginBody = @{ username = 'native_school_writer'; password = '12345678' } | ConvertTo-Json -Compress
|
||||
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $newSchoolAdminLoginBody -WebSession $newSchoolAdminSession | Out-Null
|
||||
$classCreateBody = @{ name = '迁移测试班'; grade = '2026级' } | ConvertTo-Json -Compress
|
||||
$classCreateResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/classes" -Method Post -ContentType 'application/json' -Body $classCreateBody -WebSession $newSchoolAdminSession
|
||||
if ($classCreateResponse.StatusCode -ne 201 -or $classCreateResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||
throw 'Native class creation did not use the ASP.NET Core endpoint'
|
||||
}
|
||||
$createdClass = ($classCreateResponse.Content | ConvertFrom-Json).schoolClass
|
||||
$superClassForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/classes" -Method Post -ContentType 'application/json' -Body $classCreateBody -WebSession $nativeSession -SkipHttpErrorCheck
|
||||
if ($superClassForbidden.StatusCode -ne 403) {
|
||||
throw 'Native class creation did not preserve the school-admin boundary'
|
||||
}
|
||||
$classPatchBody = @{ name = '迁移测试一班'; active = $true } | ConvertTo-Json -Compress
|
||||
$classPatch = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/classes/$($createdClass.id)" -Method Patch -ContentType 'application/json' -Body $classPatchBody -WebSession $newSchoolAdminSession
|
||||
if ($classPatch.schoolClass.name -ne '迁移测试一班') {
|
||||
throw 'Native class update did not persist the requested name'
|
||||
}
|
||||
|
||||
$newClassAdminBody = @{
|
||||
username = 'native_class_writer'
|
||||
password = '12345678'
|
||||
displayName = '原生班级管理员'
|
||||
adminLevel = 'super'
|
||||
classId = $createdClass.id
|
||||
} | ConvertTo-Json -Compress
|
||||
$newClassAdminResponse = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/admins" -Method Post -ContentType 'application/json' -Body $newClassAdminBody -WebSession $newSchoolAdminSession
|
||||
if ($newClassAdminResponse.admin.adminLevel -ne 'class' -or $newClassAdminResponse.admin.schoolId -ne $createdSchool.id) {
|
||||
throw 'School admin did not create a class-scoped administrator'
|
||||
}
|
||||
$createdClassAdmin = $newClassAdminResponse.admin
|
||||
$newClassAdminSession = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
||||
$newClassAdminLoginBody = @{ username = 'native_class_writer'; password = '12345678' } | ConvertTo-Json -Compress
|
||||
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $newClassAdminLoginBody -WebSession $newClassAdminSession | Out-Null
|
||||
|
||||
$disableClassAdminBody = @{ active = $false; displayName = '原生班级管理员(停用)' } | ConvertTo-Json -Compress
|
||||
$disabledClassAdmin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/admins/$($createdClassAdmin.id)" -Method Patch -ContentType 'application/json' -Body $disableClassAdminBody -WebSession $newSchoolAdminSession
|
||||
if ($disabledClassAdmin.admin.displayName -ne '原生班级管理员(停用)') {
|
||||
throw 'Native administrator update did not persist the display name'
|
||||
}
|
||||
$invalidatedClassSession = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/auth/me" -WebSession $newClassAdminSession -SkipHttpErrorCheck
|
||||
$invalidatedClassIdentity = $invalidatedClassSession.Content | ConvertFrom-Json
|
||||
if ($invalidatedClassSession.StatusCode -ne 200 -or $null -ne $invalidatedClassIdentity.user) {
|
||||
throw 'Disabling an administrator did not invalidate existing sessions'
|
||||
}
|
||||
|
||||
$resetClassAdmin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/admins/$($createdClassAdmin.id)/reset-password" -Method Post -WebSession $newSchoolAdminSession
|
||||
if ($resetClassAdmin.username -ne 'native_class_writer' -or $resetClassAdmin.temporaryPassword -notmatch '^Reset-[A-Za-z0-9_-]+$') {
|
||||
throw 'Native administrator password reset returned an invalid temporary password'
|
||||
}
|
||||
$resetLoginBody = @{ username = 'native_class_writer'; password = $resetClassAdmin.temporaryPassword } | ConvertTo-Json -Compress
|
||||
$resetLogin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $resetLoginBody
|
||||
if ($resetLogin.user.username -ne 'native_class_writer') {
|
||||
throw 'The temporary administrator password is not compatible with native authentication'
|
||||
}
|
||||
|
||||
$adminStateBeforeSetting = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/admins" -WebSession $nativeSession
|
||||
$originalSelfRegistration = [bool]$adminStateBeforeSetting.selfRegistrationEnabled
|
||||
$settingBody = @{ enabled = -not $originalSelfRegistration } | ConvertTo-Json -Compress
|
||||
$settingUpdate = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/settings/self-registration" -Method Put -ContentType 'application/json' -Body $settingBody -WebSession $nativeSession
|
||||
if ($settingUpdate.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or ($settingUpdate.Content | ConvertFrom-Json).enabled -eq $originalSelfRegistration) {
|
||||
throw 'Native self-registration setting did not persist the requested value'
|
||||
}
|
||||
$settingForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/settings/self-registration" -Method Put -ContentType 'application/json' -Body $settingBody -WebSession $nativeSchoolSession -SkipHttpErrorCheck
|
||||
if ($settingForbidden.StatusCode -ne 403) {
|
||||
throw 'Native self-registration setting did not preserve the super-admin boundary'
|
||||
}
|
||||
$restoreSettingBody = @{ enabled = $originalSelfRegistration } | ConvertTo-Json -Compress
|
||||
Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/settings/self-registration" -Method Put -ContentType 'application/json' -Body $restoreSettingBody -WebSession $nativeSession | Out-Null
|
||||
|
||||
$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'
|
||||
@@ -819,6 +934,7 @@ try {
|
||||
NativeCandidateDocuments = 'passed'
|
||||
NativeCandidateAdmissions = 'passed'
|
||||
NativeAdminReads = 'passed'
|
||||
NativeAdminOrganizationWrites = 'passed'
|
||||
} | Format-List
|
||||
}
|
||||
finally {
|
||||
|
||||
Loaded 3 of 12 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user