本批“工作流调度”迁移完成:

GET /api/admin/workflow-instances
PATCH /api/admin/workflow-instances/{id}/transfer
PATCH /api/admin/workflow-instances/{id}/supervise
覆盖资料变更、报名审核、考点变更、批量报名号和成绩复议五类流程,并实现同级转交、范围校验、超级管理员监督回退及业务状态同步重置。
This commit is contained in:
biss committed 2026-07-23 10:21:24 +08:00
1 parent 7fd20caf3e
commit 2589305f3c
15 files changed
+697 -13

No files matched your search

+2
View File
@@ -43,6 +43,8 @@ ADMIN_NATIVE_OPERATIONAL_READS_ENABLED=false
ADMIN_NATIVE_CANDIDATE_MANAGEMENT_ENABLED=false
# 报名审核与缴费状态更新;必须同时启用考生/报名/缴费读取。
ADMIN_NATIVE_REGISTRATION_PAYMENT_WRITES_ENABLED=false
# 工作流收件箱、同级转交与超级管理员监督调整;必须同时启用管理端基础读取。
ADMIN_NATIVE_WORKFLOW_OPERATIONS_ENABLED=false
# 仅在首次创建空数据库时使用。部署前务必修改初始密码。
INITIAL_ADMIN_USERNAME=admin
+4 -3
View File
@@ -12,7 +12,7 @@
- [x] 招生公示与 HMAC 文书验真公开接口
- [x] 登录、自主注册、Session 与 TOTP(兼容开关默认关闭)
- [x] 考生业务
- [ ] 管理后台、审批流和考务编排(管理端基础读取、组织维护、批量报名号审批、流程配置、通知公告、考点变更审批、考生/报名/缴费读取、考生账号维护及报名缴费写入已原生化)
- [ ] 管理后台、审批流和考务编排(管理端基础读取、组织维护、批量报名号审批、流程配置、通知公告、考点变更审批、考生/报名/缴费读取、考生账号维护、报名缴费写入及工作流调度已原生化)
- [x] 考生志愿填报与招生录取查询
- [ ] Excel、文书和缓存
- [ ] 容器入口切换及 Node.js 后端移除
@@ -54,7 +54,7 @@ $env:AUTH_NATIVE_ENABLED = 'true'
$env:CANDIDATE_NATIVE_ENABLED = 'true'
```
管理后台第一批只读接口(管理上下文、仪表盘、学校、学校组织、管理员和考试列表)已经原生化,并保留超级、校级、班级管理员的权限与数据作用域。第二批覆盖学校、班级和管理员的创建与维护、管理员密码重置及自主注册开关;更新操作与审计日志在同一事务中提交,停用或重置管理员会同步失效其会话。第三批覆盖批量报名号申领的读取、提交和审批,终审会按照当前号码规则原子生成考生账号、初始密码和待补录资料。第四批覆盖报名号规则及审批流程定义的读取与维护,并保留流程层级和批量申领终审约束。第五批覆盖完整通知公告管理:超级管理员可读取手工公告与五类系统公示、创建和编辑手工公告,并控制系统公示是否公开显示;HTML 净化、显隐更新和审计日志均由 ASP.NET Core 原生处理。第六批覆盖考点考场读取、新增与修改申请、重复待审拦截及多级审批;终审会在同一事务中更新流程、审计日志和正式考点考场档案。第七批覆盖考生档案、报名记录和已审核报名的缴费记录读取,并保持三级管理员的数据作用域、证件号脱敏、考试科目及审批流投影与 Node 一致。第八批覆盖校级按班级或年级归档及恢复考生账户、超级管理员重置考生密码、三级管理员按配置流程审核考生资料;账号与流程更新、审计日志在同一事务中提交,归档和密码重置会同步失效相关 ASP.NET Core 会话。第九批覆盖报名审核流程及缴费状态更新:多步报名终审会绑定考生报名号和当前号码规则,缴费确认及撤销会维护确认时间和操作人,所有流程、业务状态与审计日志均在同一事务中提交。
管理后台第一批只读接口(管理上下文、仪表盘、学校、学校组织、管理员和考试列表)已经原生化,并保留超级、校级、班级管理员的权限与数据作用域。第二批覆盖学校、班级和管理员的创建与维护、管理员密码重置及自主注册开关;更新操作与审计日志在同一事务中提交,停用或重置管理员会同步失效其会话。第三批覆盖批量报名号申领的读取、提交和审批,终审会按照当前号码规则原子生成考生账号、初始密码和待补录资料。第四批覆盖报名号规则及审批流程定义的读取与维护,并保留流程层级和批量申领终审约束。第五批覆盖完整通知公告管理:超级管理员可读取手工公告与五类系统公示、创建和编辑手工公告,并控制系统公示是否公开显示;HTML 净化、显隐更新和审计日志均由 ASP.NET Core 原生处理。第六批覆盖考点考场读取、新增与修改申请、重复待审拦截及多级审批;终审会在同一事务中更新流程、审计日志和正式考点考场档案。第七批覆盖考生档案、报名记录和已审核报名的缴费记录读取,并保持三级管理员的数据作用域、证件号脱敏、考试科目及审批流投影与 Node 一致。第八批覆盖校级按班级或年级归档及恢复考生账户、超级管理员重置考生密码、三级管理员按配置流程审核考生资料;账号与流程更新、审计日志在同一事务中提交,归档和密码重置会同步失效相关 ASP.NET Core 会话。第九批覆盖报名审核流程及缴费状态更新:多步报名终审会绑定考生报名号和当前号码规则,缴费确认及撤销会维护确认时间和操作人,所有流程、业务状态与审计日志均在同一事务中提交。第十批覆盖五类业务的工作流收件箱、当前处理人同级转交以及超级管理员指定步骤和处理人的监督调整;重新打开业务时会同步重置业务状态,并阻止已生成账号的批次重复开启。
其余审批流和考务编排接口仍转发给 Node,因此管理端开关都要求原生认证和共享 Redis;各写入子功能还必须与只读开关一起启用:
@@ -69,9 +69,10 @@ $env:ADMIN_NATIVE_CENTERS_ENABLED = 'true'
$env:ADMIN_NATIVE_OPERATIONAL_READS_ENABLED = 'true'
$env:ADMIN_NATIVE_CANDIDATE_MANAGEMENT_ENABLED = 'true'
$env:ADMIN_NATIVE_REGISTRATION_PAYMENT_WRITES_ENABLED = 'true'
$env:ADMIN_NATIVE_WORKFLOW_OPERATIONS_ENABLED = 'true'
```
旧版 `ADMIN_NATIVE_NOTICE_WRITES_ENABLED` 仍可作为兼容别名使用。`GET /health/migration` 的 `administration.nativeReadsEnabled`、`administration.nativeOrganizationWritesEnabled`、`administration.nativeAccountBatchesEnabled`、`administration.nativeConfigurationEnabled`、`administration.nativeNoticeManagementEnabled`、`administration.nativeCentersEnabled`、`administration.nativeOperationalReadsEnabled`、`administration.nativeCandidateManagementEnabled`、`administration.nativeRegistrationPaymentWritesEnabled` 和 `administration.nativeRoutes` 会报告这些端点是否已切换。
旧版 `ADMIN_NATIVE_NOTICE_WRITES_ENABLED` 仍可作为兼容别名使用。`GET /health/migration` 的 `administration.nativeReadsEnabled`、`administration.nativeOrganizationWritesEnabled`、`administration.nativeAccountBatchesEnabled`、`administration.nativeConfigurationEnabled`、`administration.nativeNoticeManagementEnabled`、`administration.nativeCentersEnabled`、`administration.nativeOperationalReadsEnabled`、`administration.nativeCandidateManagementEnabled`、`administration.nativeRegistrationPaymentWritesEnabled`、`administration.nativeWorkflowOperationsEnabled` 和 `administration.nativeRoutes` 会报告这些端点是否已切换。
完整的宿主、静态资源、JSON 转发和 Session Cookie 冒烟测试:
+51
View File
@@ -445,6 +445,7 @@ try {
ADMIN_NATIVE_OPERATIONAL_READS_ENABLED = 'true'
ADMIN_NATIVE_CANDIDATE_MANAGEMENT_ENABLED = 'true'
ADMIN_NATIVE_REGISTRATION_PAYMENT_WRITES_ENABLED = 'true'
ADMIN_NATIVE_WORKFLOW_OPERATIONS_ENABLED = 'true'
ADMIN_NATIVE_ALLOW_MEMORY = 'true'
LegacyNode__Enabled = 'true'
LegacyNode__BaseUrl = $legacyBaseUrl
@@ -821,6 +822,34 @@ try {
}
}
foreach ($workflowSession in $operationalSessions) {
$legacyWorkflowRead = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/workflow-instances" -WebSession $workflowSession.Legacy
$nativeWorkflowRead = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances" -WebSession $workflowSession.Native
if ($nativeWorkflowRead.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw "Workflow inbox for $($workflowSession.Level) admin did not use ASP.NET Core"
}
Assert-JsonEquivalent -Expected $legacyWorkflowRead.Content -Actual $nativeWorkflowRead.Content -Label "Workflow inbox for $($workflowSession.Level) admin"
}
$workflowState = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances" -WebSession $nativeSession
$transferInstance = @($workflowState.instances | Where-Object { $_.status -eq 'pending' -and $_.assigneeId } | Select-Object -First 1)[0]
if ($null -eq $transferInstance) {
throw 'Seed data did not provide a pending workflow for transfer testing'
}
$invalidTransferBody = @{ assigneeId = 'missing-admin'; note = '无效转交测试' } | ConvertTo-Json -Compress
$invalidTransfer = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances/$($transferInstance.id)/transfer" -Method Patch -ContentType 'application/json' -Body $invalidTransferBody -WebSession $nativeSession -SkipHttpErrorCheck
if ($invalidTransfer.StatusCode -ne 400 -or $invalidTransfer.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native workflow transfer accepted an invalid target administrator'
}
$transferBody = @{ assigneeId = $transferInstance.assigneeId; note = '原生同级转交验证' } | ConvertTo-Json -Compress
$transferResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances/$($transferInstance.id)/transfer" -Method Patch -ContentType 'application/json' -Body $transferBody -WebSession $nativeSession
$transferredWorkflow = ($transferResponse.Content | ConvertFrom-Json).workflow
if ($transferResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or $transferredWorkflow.assigneeId -ne $transferInstance.assigneeId -or @($transferredWorkflow.actions | Select-Object -Last 1)[0].action -ne 'transfer') {
throw 'Native workflow transfer did not persist its action and assignee'
}
$legacyWorkflowsAfterTransfer = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/workflow-instances" -WebSession $session
$nativeWorkflowsAfterTransfer = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances" -WebSession $nativeSession
Assert-JsonEquivalent -Expected $legacyWorkflowsAfterTransfer.Content -Actual $nativeWorkflowsAfterTransfer.Content -Label 'Workflow transfer follow-up'
$archiveBody = @{
scopeType = 'class'
scopeValue = $profileUpdate.profile.classId
@@ -965,6 +994,27 @@ try {
$nativePaymentsAfterReversal = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/payments" -WebSession $nativeSession
Assert-JsonEquivalent -Expected $legacyPaymentsAfterReversal.Content -Actual $nativePaymentsAfterReversal.Content -Label 'Payment reversal follow-up'
$schoolSuperviseForbidden = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances/$($registrationReview.workflow.id)/supervise" -Method Patch -ContentType 'application/json' -Body '{}' -WebSession $nativeSchoolSession -SkipHttpErrorCheck
if ($schoolSuperviseForbidden.StatusCode -ne 403 -or $schoolSuperviseForbidden.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
throw 'Native workflow supervision did not preserve the super-admin boundary'
}
$superviseBody = @{ currentStep = 1; note = '原生监督重新打开报名流程' } | ConvertTo-Json -Compress
$superviseResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances/$($registrationReview.workflow.id)/supervise" -Method Patch -ContentType 'application/json' -Body $superviseBody -WebSession $nativeSession
$supervisedWorkflow = ($superviseResponse.Content | ConvertFrom-Json).workflow
if ($superviseResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or $supervisedWorkflow.status -ne 'pending' -or $supervisedWorkflow.currentStep -ne 1 -or @($supervisedWorkflow.actions | Select-Object -Last 1)[0].action -ne 'return') {
throw 'Native workflow supervision did not reopen the completed registration at step 1'
}
$registrationStateAfterSupervision = Invoke-RestMethod -Uri "$nativeAuthBaseUrl/api/admin/registrations" -WebSession $nativeSession
$supervisedRegistration = @($registrationStateAfterSupervision.registrations | Where-Object id -eq $nativeRegistration.registration.id)[0]
if ($null -eq $supervisedRegistration -or $supervisedRegistration.status -ne 'pending' -or $supervisedRegistration.reviewedAt) {
throw 'Native workflow supervision did not reset the registration business state'
}
foreach ($workflowSession in $operationalSessions) {
$legacyWorkflowAfterSupervision = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/workflow-instances" -WebSession $workflowSession.Legacy
$nativeWorkflowAfterSupervision = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/workflow-instances" -WebSession $workflowSession.Native
Assert-JsonEquivalent -Expected $legacyWorkflowAfterSupervision.Content -Actual $nativeWorkflowAfterSupervision.Content -Label "Workflow supervision follow-up for $($workflowSession.Level) admin"
}
$legacyCenters = Invoke-WebRequest -Uri "$legacyBaseUrl/api/admin/centers" -WebSession $session
$nativeCenters = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/admin/centers" -WebSession $nativeSession
if ($nativeCenters.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
@@ -1436,6 +1486,7 @@ try {
NativeAdminOperationalReads = 'passed'
NativeAdminCandidateManagement = 'passed'
NativeAdminRegistrationPaymentWrites = 'passed'
NativeAdminWorkflowOperations = 'passed'
} | Format-List
}
finally {
Loaded 3 of 15 files, more files were not shown because too many files have changed in this diff. Show more