209 lines
8.5 KiB
PowerShell
209 lines
8.5 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
||
|
||
$root = Split-Path -Parent $PSScriptRoot
|
||
$pagesPath = Join-Path $root 'pages.json'
|
||
$mappingPath = Join-Path $root 'docs/接口与页面映射总表.md'
|
||
$planPath = Join-Path $root 'docs/家谱项目全量治理实施计划.md'
|
||
|
||
if (-not (Test-Path -LiteralPath $mappingPath -PathType Leaf)) {
|
||
throw '缺少页面与接口关系的唯一总表:docs/接口与页面映射总表.md'
|
||
}
|
||
|
||
$pagesDocument = Get-Content -Raw -Encoding UTF8 -LiteralPath $pagesPath | ConvertFrom-Json
|
||
$registeredRoutes = @($pagesDocument.pages | ForEach-Object { $_.path })
|
||
$mappingLines = @(Get-Content -Encoding UTF8 -LiteralPath $mappingPath)
|
||
$mappingText = $mappingLines -join "`n"
|
||
$planLines = @(Get-Content -Encoding UTF8 -LiteralPath $planPath)
|
||
$planText = $planLines -join "`n"
|
||
|
||
# 第一步:只解析以正式页面编号开头的表格行,避免把正文中的示例路由误算成页面映射。
|
||
$routeRows = @(
|
||
foreach ($line in $mappingLines) {
|
||
if ($line -match '^\|\s*(?<code>[AGTFRNM]\d{2})\s*\|\s*[^|]+\|\s*`(?<route>pages/[^`]+)`\s*\|') {
|
||
$columns = @($line.Trim('|').Split('|') | ForEach-Object { $_.Trim() })
|
||
[pscustomobject]@{
|
||
Code = $Matches.code
|
||
Route = $Matches.route
|
||
Line = $line
|
||
Columns = $columns
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
if ($routeRows.Count -ne $registeredRoutes.Count) {
|
||
throw "页面映射行数与 pages.json 不一致:映射=$($routeRows.Count),注册=$($registeredRoutes.Count)"
|
||
}
|
||
|
||
# 第二步:每个活动页面必须完整填写业务目标、入口、流程结果、状态和接口业务域;只列编号和路由不算完成迁移。
|
||
foreach ($row in $routeRows) {
|
||
if ($row.Columns.Count -ne 9) {
|
||
throw "页面映射列数错误:$($row.Code) 应为 9 列,实际为 $($row.Columns.Count)"
|
||
}
|
||
foreach ($columnIndex in 1..7) {
|
||
if ([string]::IsNullOrWhiteSpace($row.Columns[$columnIndex])) {
|
||
throw "页面映射存在空业务列:$($row.Code) 第 $($columnIndex + 1) 列"
|
||
}
|
||
}
|
||
}
|
||
|
||
# 第三步:页面编号和路由都必须唯一;重复行会制造两个互相冲突的当前合同。
|
||
$duplicateCodes = @($routeRows | Group-Object Code | Where-Object Count -gt 1)
|
||
if ($duplicateCodes.Count -gt 0) {
|
||
throw "页面映射存在重复编号:$($duplicateCodes.Name -join ', ')"
|
||
}
|
||
|
||
$duplicateRoutes = @($routeRows | Group-Object Route | Where-Object Count -gt 1)
|
||
if ($duplicateRoutes.Count -gt 0) {
|
||
throw "页面映射存在重复路由:$($duplicateRoutes.Name -join ', ')"
|
||
}
|
||
|
||
# 第四步:总表顺序和路由集合必须与 pages.json 精确一致,新增或删除页面时必须同轮更新唯一总表。
|
||
$mappedRoutes = @($routeRows | ForEach-Object Route)
|
||
$missingRoutes = @($registeredRoutes | Where-Object { $_ -notin $mappedRoutes })
|
||
$staleRoutes = @($mappedRoutes | Where-Object { $_ -notin $registeredRoutes })
|
||
if ($missingRoutes.Count -gt 0 -or $staleRoutes.Count -gt 0) {
|
||
throw "页面映射与 pages.json 不相等:缺失=[$($missingRoutes -join ', ')];陈旧=[$($staleRoutes -join ', ')]"
|
||
}
|
||
|
||
for ($index = 0; $index -lt $registeredRoutes.Count; $index += 1) {
|
||
if ($mappedRoutes[$index] -ne $registeredRoutes[$index]) {
|
||
throw "页面映射顺序错误:第 $($index + 1) 行应为 $($registeredRoutes[$index]),实际为 $($mappedRoutes[$index])"
|
||
}
|
||
}
|
||
|
||
# 第五步:只允许 A04 和 T01 使用已经形成证据的专项结论;其余页面不能提前冒充完成 OpenAPI 审查。
|
||
$prematureRows = @(
|
||
$routeRows | Where-Object {
|
||
if ($_.Code -eq 'A04') { return $_.Line -notmatch '已核对注册成功响应为 `LoginResult`' -or $_.Line -notmatch '成功建立登录态并进入 G01' }
|
||
if ($_.Code -eq 'T01') { return $_.Line -notmatch '专项已核对:现有递归 `LineagePersonTreeView` 不满足' }
|
||
return $_.Line -notmatch '待对应业务阶段 OpenAPI 审查'
|
||
}
|
||
)
|
||
if ($prematureRows.Count -gt 0) {
|
||
throw "存在没有准确表达接口审查状态的活动页面:$($prematureRows.Code -join ', ')"
|
||
}
|
||
|
||
foreach ($heading in @(
|
||
'## 一、权威边界',
|
||
'## 二、全局业务与交互合同',
|
||
'## 三、52 个活动页面映射',
|
||
'## 四、封存与已移除页面',
|
||
'## 五、后续接口审查填充规则'
|
||
)) {
|
||
if (-not $mappingText.Contains($heading)) {
|
||
throw "接口与页面映射总表缺少章节:$heading"
|
||
}
|
||
}
|
||
|
||
# 第六步:锁定旧资料中仍然有效的关键业务事实,避免未来把表格保留却悄悄清空真正的产品合同。
|
||
foreach ($requiredFact in @(
|
||
'8–32',
|
||
'A01 在本地校验通过后、登录请求前触发',
|
||
'A05 在请求发送短信验证码前触发',
|
||
'A04 当前临时在完整注册表单提交后触发',
|
||
'聚焦密码框',
|
||
'审核中记录只展示进度和“撤回申请”',
|
||
'被拒绝记录展示原因和“修改后重新提交”',
|
||
'仅成员可见',
|
||
'公开可申请',
|
||
'LineageGraphWindow',
|
||
'EMPTY/POPULATED',
|
||
'entryPersonIds',
|
||
'genealogyRootPersonIds',
|
||
'redactedGenealogyRootCount',
|
||
'rootVisibility',
|
||
'pathCompleteness',
|
||
'ancestorPathSegments',
|
||
'anchorPersonId',
|
||
'schemaVersion',
|
||
'returnedNodeCount',
|
||
'partnerRelationship',
|
||
'relationshipKind',
|
||
'parentRole',
|
||
'LINEAGE_QUERY_INVALID',
|
||
'LINEAGE_FOCUS_NOT_AVAILABLE',
|
||
'RELATIONSHIP_PATCH_EMPTY',
|
||
'sceneVersion',
|
||
'/genealogy/app/v2/',
|
||
'API-T01-001',
|
||
'单个原生页面实例',
|
||
'注册成功响应为 `LoginResult`',
|
||
'aria-describedby',
|
||
'邀请码直接加入且不生成审核记录',
|
||
'appApi',
|
||
'500',
|
||
'20',
|
||
'100ms',
|
||
'44dp',
|
||
'导航栈语义统一',
|
||
'跨页面领域数据持久化',
|
||
'全局文字层级和无障碍第二轮',
|
||
'A02 账号登录',
|
||
'A06 登录状态',
|
||
'G02',
|
||
'G04',
|
||
'G07',
|
||
'T02'
|
||
)) {
|
||
if (-not $mappingText.Contains($requiredFact)) {
|
||
throw "接口与页面映射总表缺少关键业务事实:$requiredFact"
|
||
}
|
||
}
|
||
|
||
# 第七步:在可执行注册表落地前解析实施计划中的 52 行路由表,防止计划与页面总表各自正确却互相漂移。
|
||
$plannedRouteRows = @(
|
||
foreach ($line in $planLines) {
|
||
if ($line -match '^\|\s*(?<code>[AGTFRNM]\d{2})\s*\|\s*(?<kind>认证根页|根页|普通页|流程页|单实例页)\s*\|') {
|
||
$columns = @($line.Trim('|').Split('|') | ForEach-Object { $_.Trim() })
|
||
[pscustomobject]@{
|
||
Code = $Matches.code
|
||
Kind = $Matches.kind
|
||
Columns = $columns
|
||
Line = $line
|
||
}
|
||
}
|
||
}
|
||
)
|
||
|
||
if ($plannedRouteRows.Count -ne 52) {
|
||
throw "实施计划路由表不是 52 行:实际=$($plannedRouteRows.Count)"
|
||
}
|
||
$duplicatePlannedCodes = @($plannedRouteRows | Group-Object Code | Where-Object Count -gt 1)
|
||
if ($duplicatePlannedCodes.Count -gt 0) {
|
||
throw "实施计划路由表存在重复编号:$($duplicatePlannedCodes.Name -join ', ')"
|
||
}
|
||
$mappedCodes = @($routeRows | ForEach-Object Code)
|
||
$plannedCodes = @($plannedRouteRows | ForEach-Object Code)
|
||
$missingPlannedCodes = @($mappedCodes | Where-Object { $_ -notin $plannedCodes })
|
||
$stalePlannedCodes = @($plannedCodes | Where-Object { $_ -notin $mappedCodes })
|
||
if ($missingPlannedCodes.Count -gt 0 -or $stalePlannedCodes.Count -gt 0) {
|
||
throw "实施计划路由表与页面映射编号不相等:缺失=[$($missingPlannedCodes -join ', ')];陈旧=[$($stalePlannedCodes -join ', ')]"
|
||
}
|
||
$plannedRootCodes = @($plannedRouteRows | Where-Object { $_.Kind -in @('认证根页', '根页') } | ForEach-Object Code)
|
||
if (($plannedRootCodes -join ',') -ne 'A01,G01,F01,M01') {
|
||
throw "实施计划四个根语义错误:$($plannedRootCodes -join ',')"
|
||
}
|
||
$plannedBusinessRoots = @($plannedRouteRows | Where-Object Kind -eq '根页' | ForEach-Object Code)
|
||
if (($plannedBusinessRoots -join ',') -ne 'G01,F01,M01') {
|
||
throw "实施计划业务根页必须仅为 G01/F01/M01:$($plannedBusinessRoots -join ',')"
|
||
}
|
||
if ($planText -notmatch 'T03: defineRoute\([^\r\n]*parentParamMap: \{ selectedId: "personId" \}') {
|
||
throw '实施计划缺少 T03 直接进入时 personId 到 selectedId 的父页参数映射'
|
||
}
|
||
if ($planText -notmatch 'openPage\("N02", \{ id \}, "N01"\)') {
|
||
throw '实施计划没有把 N01 打开 N02 的真实来源固定为 N01'
|
||
}
|
||
$plannedTableText = ($plannedRouteRows | ForEach-Object Line) -join "`n"
|
||
foreach ($forbiddenPlannedParam in @('genealogyId,step', 'genealogyName')) {
|
||
if ($plannedTableText.Contains($forbiddenPlannedParam)) {
|
||
throw "实施计划路由表仍含已删除参数:$forbiddenPlannedParam"
|
||
}
|
||
}
|
||
|
||
if ($mappingText -match '汤正华堂侄') {
|
||
throw '当前产品文档不得使用具体姓名示例,应改为“某某某堂侄”等通用占位表达'
|
||
}
|
||
|
||
Write-Output "INTERFACE-PAGE-MAPPING-CONTRACT PASS ROUTES=$($routeRows.Count)"
|