已完成管理后台第三批 ASP.NET Core 10 迁移:批量报名号申领与审批。
原生接口:
GET /api/admin/candidate-account-batches
POST /api/admin/candidate-account-batches
PATCH /api/admin/candidate-account-batches/{batchId}
实现包含:
校级管理员按班级提交名额
可配置多级审批流与待办负载分配
当前处理人及管理员层级校验
审批通过、退回和重复审批保护
终审时原子生成考生账号、报名号、初始密码和待补录资料
生成账号兼容现有 PBKDF2 登录及首次强制改密
This commit is contained in:
1 parent
875e59b6ce
commit
f7c34247fd
13 files changed
+567
-28
No files matched your search
@@ -29,6 +29,8 @@ CANDIDATE_NATIVE_ENABLED=false
|
|||||||
ADMIN_NATIVE_READS_ENABLED=false
|
ADMIN_NATIVE_READS_ENABLED=false
|
||||||
# 学校、班级、管理员维护及自主注册开关切换;同样要求原生认证和共享 Redis。
|
# 学校、班级、管理员维护及自主注册开关切换;同样要求原生认证和共享 Redis。
|
||||||
ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED=false
|
ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED=false
|
||||||
|
# 批量报名号申领、审批与账号生成;必须同时启用管理端只读接口。
|
||||||
|
ADMIN_NATIVE_ACCOUNT_BATCHES_ENABLED=false
|
||||||
|
|
||||||
# 仅在首次创建空数据库时使用。部署前务必修改初始密码。
|
# 仅在首次创建空数据库时使用。部署前务必修改初始密码。
|
||||||
INITIAL_ADMIN_USERNAME=admin
|
INITIAL_ADMIN_USERNAME=admin
|
||||||
|
|||||||
+4
-3
@@ -12,7 +12,7 @@
|
|||||||
- [x] 招生公示与 HMAC 文书验真公开接口
|
- [x] 招生公示与 HMAC 文书验真公开接口
|
||||||
- [x] 登录、自主注册、Session 与 TOTP(兼容开关默认关闭)
|
- [x] 登录、自主注册、Session 与 TOTP(兼容开关默认关闭)
|
||||||
- [x] 考生业务
|
- [x] 考生业务
|
||||||
- [ ] 管理后台、审批流和考务编排(管理端读取与组织维护已部分原生化)
|
- [ ] 管理后台、审批流和考务编排(管理端读取、组织维护及批量报名号审批已原生化)
|
||||||
- [x] 考生志愿填报与招生录取查询
|
- [x] 考生志愿填报与招生录取查询
|
||||||
- [ ] Excel、文书和缓存
|
- [ ] Excel、文书和缓存
|
||||||
- [ ] 容器入口切换及 Node.js 后端移除
|
- [ ] 容器入口切换及 Node.js 后端移除
|
||||||
@@ -54,7 +54,7 @@ $env:AUTH_NATIVE_ENABLED = 'true'
|
|||||||
$env:CANDIDATE_NATIVE_ENABLED = 'true'
|
$env:CANDIDATE_NATIVE_ENABLED = 'true'
|
||||||
```
|
```
|
||||||
|
|
||||||
管理后台第一批只读接口(管理上下文、仪表盘、学校、学校组织、管理员和考试列表)已经原生化,并保留超级、校级、班级管理员的权限与数据作用域。第二批覆盖学校、班级和管理员的创建与维护、管理员密码重置及自主注册开关;更新操作与审计日志在同一事务中提交,停用或重置管理员会同步失效其会话。
|
管理后台第一批只读接口(管理上下文、仪表盘、学校、学校组织、管理员和考试列表)已经原生化,并保留超级、校级、班级管理员的权限与数据作用域。第二批覆盖学校、班级和管理员的创建与维护、管理员密码重置及自主注册开关;更新操作与审计日志在同一事务中提交,停用或重置管理员会同步失效其会话。第三批覆盖批量报名号申领的读取、提交和审批,终审会按照当前号码规则原子生成考生账号、初始密码和待补录资料。
|
||||||
|
|
||||||
其余审批流和考务编排接口仍转发给 Node,因此两个管理端开关都要求原生认证和共享 Redis;组织维护开关还必须与只读开关一起启用:
|
其余审批流和考务编排接口仍转发给 Node,因此两个管理端开关都要求原生认证和共享 Redis;组织维护开关还必须与只读开关一起启用:
|
||||||
|
|
||||||
@@ -62,9 +62,10 @@ $env:CANDIDATE_NATIVE_ENABLED = 'true'
|
|||||||
$env:AUTH_NATIVE_ENABLED = 'true'
|
$env:AUTH_NATIVE_ENABLED = 'true'
|
||||||
$env:ADMIN_NATIVE_READS_ENABLED = 'true'
|
$env:ADMIN_NATIVE_READS_ENABLED = 'true'
|
||||||
$env:ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED = 'true'
|
$env:ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED = 'true'
|
||||||
|
$env:ADMIN_NATIVE_ACCOUNT_BATCHES_ENABLED = 'true'
|
||||||
```
|
```
|
||||||
|
|
||||||
`GET /health/migration` 的 `administration.nativeReadsEnabled`、`administration.nativeOrganizationWritesEnabled` 和 `administration.nativeRoutes` 会报告这些端点是否已切换。
|
`GET /health/migration` 的 `administration.nativeReadsEnabled`、`administration.nativeOrganizationWritesEnabled`、`administration.nativeAccountBatchesEnabled` 和 `administration.nativeRoutes` 会报告这些端点是否已切换。
|
||||||
|
|
||||||
完整的宿主、静态资源、JSON 转发和 Session Cookie 冒烟测试:
|
完整的宿主、静态资源、JSON 转发和 Session Cookie 冒烟测试:
|
||||||
|
|
||||||
|
|||||||
@@ -424,6 +424,7 @@ try {
|
|||||||
CANDIDATE_NATIVE_ALLOW_MEMORY = 'true'
|
CANDIDATE_NATIVE_ALLOW_MEMORY = 'true'
|
||||||
ADMIN_NATIVE_READS_ENABLED = 'true'
|
ADMIN_NATIVE_READS_ENABLED = 'true'
|
||||||
ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED = 'true'
|
ADMIN_NATIVE_ORGANIZATION_WRITES_ENABLED = 'true'
|
||||||
|
ADMIN_NATIVE_ACCOUNT_BATCHES_ENABLED = 'true'
|
||||||
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
|
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
|
||||||
LegacyNode__Enabled = 'true'
|
LegacyNode__Enabled = 'true'
|
||||||
LegacyNode__BaseUrl = $legacyBaseUrl
|
LegacyNode__BaseUrl = $legacyBaseUrl
|
||||||
@@ -738,6 +739,58 @@ try {
|
|||||||
throw 'Native admins route did not preserve the class-admin boundary'
|
throw 'Native admins route did not preserve the class-admin boundary'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$legacySuperBatches = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/candidate-account-batches" -WebSession $session
|
||||||
|
$nativeSuperBatches = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches" -WebSession $nativeSession
|
||||||
|
if ($nativeSuperBatches.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||||
|
throw 'Native candidate-account batch read did not use ASP.NET Core'
|
||||||
|
}
|
||||||
|
Assert-JsonEquivalent -Expected $legacySuperBatches.Content -Actual $nativeSuperBatches.Content -Label 'Super-admin candidate-account batches'
|
||||||
|
$legacySchoolBatches = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/candidate-account-batches" -WebSession $legacySchoolSession
|
||||||
|
$nativeSchoolBatches = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches" -WebSession $nativeSchoolSession
|
||||||
|
Assert-JsonEquivalent -Expected $legacySchoolBatches.Content -Actual $nativeSchoolBatches.Content -Label 'School-admin candidate-account batches'
|
||||||
|
$classBatchesForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches" -WebSession $nativeClassSession -SkipHttpErrorCheck
|
||||||
|
if ($classBatchesForbidden.StatusCode -ne 403 -or $classBatchesForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||||
|
throw 'Native candidate-account batches did not preserve the candidates.write boundary'
|
||||||
|
}
|
||||||
|
|
||||||
|
$schoolBatchState = $nativeSchoolBatches.Content | ConvertFrom-Json
|
||||||
|
$batchClass = @($schoolBatchState.classes)[0]
|
||||||
|
if ($null -eq $batchClass) {
|
||||||
|
throw 'Seed data did not provide an active class for account-batch smoke testing'
|
||||||
|
}
|
||||||
|
$batchSubmitBody = @{ quotas = @(@{ classId = $batchClass.id; count = 2 }) } | ConvertTo-Json -Depth 4 -Compress
|
||||||
|
$batchSubmitResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches" -Method Post -ContentType 'application/json' -Body $batchSubmitBody -WebSession $nativeSchoolSession
|
||||||
|
if ($batchSubmitResponse.StatusCode -ne 202 -or $batchSubmitResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||||
|
throw 'Native candidate-account batch submission did not use ASP.NET Core'
|
||||||
|
}
|
||||||
|
$submittedBatch = ($batchSubmitResponse.Content | ConvertFrom-Json).batch
|
||||||
|
if ($submittedBatch.status -ne 'pending' -or $submittedBatch.totalCount -ne 2 -or $submittedBatch.workflow.status -ne 'pending') {
|
||||||
|
throw 'Native candidate-account batch submission did not create its workflow atomically'
|
||||||
|
}
|
||||||
|
$schoolBatchReview = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches/$($submittedBatch.id)" -Method Patch -ContentType 'application/json' -Body (@{ status = 'approved'; reviewNote = '越权审批' } | ConvertTo-Json -Compress) -WebSession $nativeSchoolSession -SkipHttpErrorCheck
|
||||||
|
if ($schoolBatchReview.StatusCode -ne 403) {
|
||||||
|
throw 'Native account-batch review did not enforce the current workflow assignee'
|
||||||
|
}
|
||||||
|
$batchApproveBody = @{ status = 'approved'; reviewNote = '原生终审通过' } | ConvertTo-Json -Compress
|
||||||
|
$batchApproveResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches/$($submittedBatch.id)" -Method Patch -ContentType 'application/json' -Body $batchApproveBody -WebSession $nativeSession
|
||||||
|
if ($batchApproveResponse.StatusCode -ne 200 -or $batchApproveResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||||
|
throw 'Native candidate-account batch approval did not use ASP.NET Core'
|
||||||
|
}
|
||||||
|
$approvedBatch = ($batchApproveResponse.Content | ConvertFrom-Json).batch
|
||||||
|
if ($approvedBatch.status -ne 'approved' -or @($approvedBatch.items).Count -ne 2 -or @($approvedBatch.items | Where-Object { -not $_.candidateNumber -or $_.initialPassword -notmatch '^Init-' }).Count -ne 0) {
|
||||||
|
throw 'Native candidate-account batch approval did not generate all candidate credentials'
|
||||||
|
}
|
||||||
|
$generatedAccount = @($approvedBatch.items)[0]
|
||||||
|
$generatedLoginBody = @{ username = $generatedAccount.candidateNumber; password = $generatedAccount.initialPassword } | ConvertTo-Json -Compress
|
||||||
|
$generatedLogin = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/auth/login" -Method Post -ContentType 'application/json' -Body $generatedLoginBody
|
||||||
|
if ($generatedLogin.user.candidateNumber -ne $generatedAccount.candidateNumber -or $generatedLogin.user.mustChangePassword -ne $true) {
|
||||||
|
throw 'Generated candidate credentials are not compatible with native authentication'
|
||||||
|
}
|
||||||
|
$duplicateBatchApproval = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/candidate-account-batches/$($submittedBatch.id)" -Method Patch -ContentType 'application/json' -Body $batchApproveBody -WebSession $nativeSession -SkipHttpErrorCheck
|
||||||
|
if ($duplicateBatchApproval.StatusCode -ne 404) {
|
||||||
|
throw 'Native candidate-account batch approval was not idempotently protected'
|
||||||
|
}
|
||||||
|
|
||||||
$schoolCreateBody = @{
|
$schoolCreateBody = @{
|
||||||
name = '原生迁移测试学校'
|
name = '原生迁移测试学校'
|
||||||
code = 'NATIVE_SMOKE'
|
code = 'NATIVE_SMOKE'
|
||||||
@@ -935,6 +988,7 @@ try {
|
|||||||
NativeCandidateAdmissions = 'passed'
|
NativeCandidateAdmissions = 'passed'
|
||||||
NativeAdminReads = 'passed'
|
NativeAdminReads = 'passed'
|
||||||
NativeAdminOrganizationWrites = 'passed'
|
NativeAdminOrganizationWrites = 'passed'
|
||||||
|
NativeAdminAccountBatches = 'passed'
|
||||||
} | Format-List
|
} | Format-List
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
Loaded 3 of 13 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user