原生成绩查询、科目/总分排名、达线判断、等级计算及成绩单防伪码:[CandidateService.Results.cs (line 11)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Candidate/CandidateService.Results.cs:11)
原生成绩复议提交,工作流、分配人与审计日志同事务写入。 原生准考证下载窗口校验及 HTML 文书生成:[CandidateAdmitCardRenderer.cs (line 9)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Infrastructure/Candidate/CandidateAdmitCardRenderer.cs:9) 三个路由已切换到 ASP.NET Core:[NativeCandidateEndpoints.cs (line 61)](C:/Users/BI/Documents/EIS-dotnet/src/Eis.Web/Candidate/NativeCandidateEndpoints.cs:61) 更新迁移说明:[MIGRATION.md (line 50)](C:/Users/BI/Documents/EIS-dotnet/MIGRATION.md:50)
This commit is contained in:
1 parent
aecb0a04e7
commit
4417d6d674
12 files changed
+774
-16
No files matched your search
+1
-1
@@ -47,7 +47,7 @@ $env:AUTH_NATIVE_ENABLED = 'true'
|
|||||||
|
|
||||||
开发环境未配置 Redis 时可以使用进程内状态独立验证原生认证。生产环境以及仍需访问 Node 受保护接口的联调环境必须配置 `REDIS_URL` 或 `REDIS_SESSION_URL`;两个运行时会复用相同逻辑库和 `exam-information:auth` 键前缀,从而共享登录会话。`GET /health/migration` 会报告 `authentication.nativeEnabled`、状态后端和跨运行时会话共享能力。
|
开发环境未配置 Redis 时可以使用进程内状态独立验证原生认证。生产环境以及仍需访问 Node 受保护接口的联调环境必须配置 `REDIS_URL` 或 `REDIS_SESSION_URL`;两个运行时会复用相同逻辑库和 `exam-information:auth` 键前缀,从而共享登录会话。`GET /health/migration` 会报告 `authentication.nativeEnabled`、状态后端和跨运行时会话共享能力。
|
||||||
|
|
||||||
考生域核心端点(首页、通知、个人资料读取与提交、可报名考试、我的报名读取与提交)可通过以下开关原生运行;该开关必须与原生认证及共享 Redis 同时启用。成绩、准考证、复议和志愿填报仍会继续转发给 Node:
|
考生域核心端点(首页、通知、个人资料读取与提交、可报名考试、我的报名读取与提交、成绩与总分排名、成绩单防伪码、成绩复议、准考证下载)可通过以下开关原生运行;该开关必须与原生认证及共享 Redis 同时启用。志愿填报与录取查询仍会继续转发给 Node:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$env:AUTH_NATIVE_ENABLED = 'true'
|
$env:AUTH_NATIVE_ENABLED = 'true'
|
||||||
|
|||||||
@@ -104,6 +104,28 @@ function Assert-JsonEquivalent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function Assert-CandidateResultsEquivalent {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string] $Expected,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string] $Actual
|
||||||
|
)
|
||||||
|
|
||||||
|
$expectedNode = [System.Text.Json.Nodes.JsonNode]::Parse($Expected)
|
||||||
|
$actualNode = [System.Text.Json.Nodes.JsonNode]::Parse($Actual)
|
||||||
|
foreach ($node in @($expectedNode, $actualNode)) {
|
||||||
|
foreach ($summary in $node['summaries'].AsArray()) {
|
||||||
|
$summary.AsObject().Remove('verificationQr') | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not [System.Text.Json.Nodes.JsonNode]::DeepEquals($expectedNode, $actualNode)) {
|
||||||
|
$difference = Find-JsonDifference -Expected $expectedNode -Actual $actualNode -Path '$'
|
||||||
|
throw "Candidate route 'results' JSON payload differs from the legacy API at $difference"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Find-JsonDifference {
|
function Find-JsonDifference {
|
||||||
param(
|
param(
|
||||||
[AllowNull()]
|
[AllowNull()]
|
||||||
@@ -407,6 +429,51 @@ try {
|
|||||||
}
|
}
|
||||||
Assert-JsonEquivalent -Expected $legacyCandidateResponse.Content -Actual $nativeCandidateResponse.Content -Label "Candidate route '$candidateRoute'"
|
Assert-JsonEquivalent -Expected $legacyCandidateResponse.Content -Actual $nativeCandidateResponse.Content -Label "Candidate route '$candidateRoute'"
|
||||||
}
|
}
|
||||||
|
$legacyCandidateResultsResponse = Invoke-WebRequest -Uri "$legacyBaseUrl/api/candidate/results" -WebSession $candidateSession
|
||||||
|
$nativeCandidateResultsResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/results" -WebSession $nativeCandidateSession
|
||||||
|
if ($nativeCandidateResultsResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||||
|
throw "Candidate route 'results' did not use the native ASP.NET Core endpoint"
|
||||||
|
}
|
||||||
|
Assert-CandidateResultsEquivalent -Expected $legacyCandidateResultsResponse.Content -Actual $nativeCandidateResultsResponse.Content
|
||||||
|
$nativeCandidateResults = $nativeCandidateResultsResponse.Content | ConvertFrom-Json
|
||||||
|
if (@($nativeCandidateResults.summaries | Where-Object { $_.verificationQr -match '^data:image/png;base64,' }).Count -eq 0) {
|
||||||
|
throw 'Native candidate results did not include a local PNG verification QR code'
|
||||||
|
}
|
||||||
|
$appealableResult = @($nativeCandidateResults.results | Where-Object { $null -eq $_.appeal } | Select-Object -First 1)[0]
|
||||||
|
if ($null -ne $appealableResult) {
|
||||||
|
$shortAppeal = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/results/$($appealableResult.id)/appeals" -Method Post -ContentType 'application/json' -Body '{"reason":"短"}' -WebSession $nativeCandidateSession -SkipHttpErrorCheck
|
||||||
|
if ($shortAppeal.StatusCode -ne 400 -or $shortAppeal.Headers['X-EIS-Implementation'] -ne 'aspnet-core') {
|
||||||
|
throw 'Native score appeal did not validate the minimum reason length'
|
||||||
|
}
|
||||||
|
$appealBody = @{ reason = '迁移冒烟验证:成绩与个人估分差异较大,请复核计分。' } | ConvertTo-Json -Compress
|
||||||
|
$appealResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/results/$($appealableResult.id)/appeals" -Method Post -ContentType 'application/json' -Body $appealBody -WebSession $nativeCandidateSession
|
||||||
|
$appeal = $appealResponse.Content | ConvertFrom-Json
|
||||||
|
if ($appealResponse.StatusCode -ne 201 -or $appealResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or $appeal.workflow.status -ne 'pending') {
|
||||||
|
throw 'Native score appeal did not persist a pending workflow'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$admitProcess = Start-TestProcess -FileName $nodeExecutable -ArgumentList @(
|
||||||
|
'tests/helpers/prepare-candidate-admit-smoke.mjs', $smokeDatabasePath, '2026-HZ01-F-0001'
|
||||||
|
) -Environment $nodeEnvironment
|
||||||
|
if (-not $admitProcess.WaitForExit(10000)) {
|
||||||
|
$admitProcess.Kill($true)
|
||||||
|
throw 'Timed out while preparing the candidate admit-card smoke data'
|
||||||
|
}
|
||||||
|
$admitRegistrationId = $admitProcess.StandardOutput.ReadToEnd().Trim()
|
||||||
|
$admitError = $admitProcess.StandardError.ReadToEnd()
|
||||||
|
if ($admitProcess.ExitCode -ne 0 -or -not $admitRegistrationId) {
|
||||||
|
throw "Could not prepare the candidate admit-card smoke data`n$admitError"
|
||||||
|
}
|
||||||
|
$admitProcess.Dispose()
|
||||||
|
$legacyAdmitResponse = Invoke-WebRequest -Uri "$legacyBaseUrl/api/candidate/registrations/$admitRegistrationId/admit-card" -WebSession $candidateSession
|
||||||
|
$nativeAdmitResponse = Invoke-WebRequest -Uri "$nativeAuthBaseUrl/api/candidate/registrations/$admitRegistrationId/admit-card" -WebSession $nativeCandidateSession
|
||||||
|
if ($nativeAdmitResponse.StatusCode -ne 200 -or
|
||||||
|
$nativeAdmitResponse.Headers['X-EIS-Implementation'] -ne 'aspnet-core' -or
|
||||||
|
$nativeAdmitResponse.Headers['Content-Disposition'] -notmatch '^attachment;' -or
|
||||||
|
$nativeAdmitResponse.Content -notmatch '考试考场序号' -or
|
||||||
|
$nativeAdmitResponse.Content -ne $legacyAdmitResponse.Content) {
|
||||||
|
throw 'Native admit-card download did not match the legacy HTML document'
|
||||||
|
}
|
||||||
|
|
||||||
$registrationSchool = @($homePayload.schools | Select-Object -First 1)[0]
|
$registrationSchool = @($homePayload.schools | Select-Object -First 1)[0]
|
||||||
$registrationClass = @($homePayload.classes | Where-Object schoolId -eq $registrationSchool.id | Select-Object -First 1)[0]
|
$registrationClass = @($homePayload.classes | Where-Object schoolId -eq $registrationSchool.id | Select-Object -First 1)[0]
|
||||||
@@ -631,6 +698,7 @@ try {
|
|||||||
NativeAuthentication = 'passed'
|
NativeAuthentication = 'passed'
|
||||||
NativeCandidateReads = 'passed'
|
NativeCandidateReads = 'passed'
|
||||||
NativeCandidateWrites = 'passed'
|
NativeCandidateWrites = 'passed'
|
||||||
|
NativeCandidateDocuments = 'passed'
|
||||||
} | Format-List
|
} | Format-List
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ namespace Eis.Application.Candidate;
|
|||||||
|
|
||||||
public sealed record CandidateEndpointResult(int StatusCode, JsonObject Body);
|
public sealed record CandidateEndpointResult(int StatusCode, JsonObject Body);
|
||||||
|
|
||||||
|
public sealed record CandidateDocumentResult(
|
||||||
|
int StatusCode,
|
||||||
|
string? Content,
|
||||||
|
string? FileName,
|
||||||
|
JsonObject? Error);
|
||||||
|
|
||||||
public interface ICandidateService
|
public interface ICandidateService
|
||||||
{
|
{
|
||||||
Task<CandidateEndpointResult> GetDashboardAsync(string sessionToken, CancellationToken cancellationToken);
|
Task<CandidateEndpointResult> GetDashboardAsync(string sessionToken, CancellationToken cancellationToken);
|
||||||
@@ -16,6 +22,11 @@ public interface ICandidateService
|
|||||||
|
|
||||||
Task<CandidateEndpointResult> GetRegistrationsAsync(string sessionToken, CancellationToken cancellationToken);
|
Task<CandidateEndpointResult> GetRegistrationsAsync(string sessionToken, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<CandidateEndpointResult> GetResultsAsync(
|
||||||
|
string sessionToken,
|
||||||
|
string verificationBaseUrl,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
Task<CandidateEndpointResult> UpdateProfileAsync(
|
Task<CandidateEndpointResult> UpdateProfileAsync(
|
||||||
string sessionToken,
|
string sessionToken,
|
||||||
JsonObject body,
|
JsonObject body,
|
||||||
@@ -25,4 +36,15 @@ public interface ICandidateService
|
|||||||
string sessionToken,
|
string sessionToken,
|
||||||
JsonObject body,
|
JsonObject body,
|
||||||
CancellationToken cancellationToken);
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<CandidateEndpointResult> CreateScoreAppealAsync(
|
||||||
|
string sessionToken,
|
||||||
|
string resultId,
|
||||||
|
JsonObject body,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<CandidateDocumentResult> GetAdmitCardAsync(
|
||||||
|
string sessionToken,
|
||||||
|
string registrationId,
|
||||||
|
CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
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