保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。 保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。 验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。 包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
90 lines
5.9 KiB
PowerShell
90 lines
5.9 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
$base = 'http://s101.local:18080/'
|
|
$outPath = Join-Path $PSScriptRoot 'M2-C3c正式互加好友实测-final-2026-09-30.json'
|
|
$journal = [Collections.Generic.List[object]]::new()
|
|
$sessions = @{}; $profiles = @{}; $before = @{}; $after = @{}
|
|
$report = [ordered]@{ time=(Get-Date).ToString('o'); api=$base; accounts=@('101','102'); requests=$journal; kvInputs=@(); before=$before; after=$after; error=$null; mutual=$false }
|
|
function Save-Evidence { $report | ConvertTo-Json -Depth 16 | Set-Content -Encoding utf8 $outPath }
|
|
function Send-Original([string]$id, [string]$route, $body, [bool]$mutation=$false) {
|
|
$row = [ordered]@{ account=$id; route=$route; mutation=$mutation; http=$null; code=$null; error=$null }
|
|
$journal.Add($row); Save-Evidence
|
|
try {
|
|
$json = ConvertTo-Json -InputObject $body -Depth 10 -Compress
|
|
$reply = Invoke-WebRequest -Uri ($base+$route) -Method Post -ContentType 'application/json' -Body $json -WebSession $sessions[$id] -MaximumRedirection 0 -TimeoutSec 20 -SkipHttpErrorCheck
|
|
$row.http = [int]$reply.StatusCode
|
|
if ($row.http -ne 200) { throw "HttpFailure:$route" }
|
|
$parsed = $reply.Content | ConvertFrom-Json
|
|
if ($null -ne $parsed.code) { $row.code=$parsed.code }
|
|
Save-Evidence
|
|
return $parsed
|
|
} catch { $row.error=$_.Exception.GetType().Name; Save-Evidence; throw }
|
|
}
|
|
function Describe-Friends($data) {
|
|
if ($null -eq $data.code -or $data.code -ne 1 -or $null -eq $data.friends) { throw 'FriendListNotSuccessful' }
|
|
$items = @(foreach ($raw in @($data.friends)) {
|
|
$item = if ($raw -is [string]) { $raw | ConvertFrom-Json } else { $raw }
|
|
$idValue = $null
|
|
foreach ($key in @('PlayFabId','FriendPlayFabId','PlayerId','Id')) {
|
|
if ($null -ne $item.$key) { $idValue=[string]$item.$key; break }
|
|
}
|
|
if ([string]::IsNullOrEmpty($idValue)) { throw 'UnrecognizedFriendIdentity' }
|
|
$fields = @(foreach ($p in $item.PSObject.Properties) {
|
|
$entry = [ordered]@{ name=$p.Name; type=$(if ($null -eq $p.Value) {'null'} else {$p.Value.GetType().Name}) }
|
|
if ($p.Name -match '^(Level|LevelB|Lv|StatValue|InfiniteBuildingLevel|LastLogin|LastLoginTime|LastLoginTimeUtc)$') { $entry.value=$p.Value }
|
|
if ($p.Value -is [pscustomobject]) { $entry.childFields=@($p.Value.PSObject.Properties.Name) }
|
|
$entry
|
|
})
|
|
[ordered]@{ id=$(if ($idValue -in @('101','102')) {$idValue} else {'redacted-other'}); encodedAsString=($raw -is [string]); fields=$fields }
|
|
})
|
|
return [ordered]@{ code=$data.code; count=@($data.friends).Count; items=$items }
|
|
}
|
|
function Has-Friend($snapshot, [string]$id) { return @($snapshot.items | Where-Object {$_.id -eq $id}).Count -gt 0 }
|
|
try {
|
|
foreach ($id in @('101','102')) {
|
|
$sessions[$id] = [Microsoft.PowerShell.Commands.WebRequestSession]::new()
|
|
$auth = Send-Original $id 'account/Login' @{playerId=$id}
|
|
if ($null -eq $auth.state -or $auth.state -ne 0 -or $auth.newCreate -ne $false) { throw 'ExistingAccountLoginNotConfirmed' }
|
|
$kv = Send-Original $id 'playerdata/Get' @{keys=@('Lv','NewPlayerDataDic','DisplayName','AvatarUrl','InfiniteBuildingData','LastLoginTimeUtc')}
|
|
if ($null -eq $kv.data) { throw 'MissingKVData' }
|
|
$source='Lv'; $levelText=[string]$kv.data.Lv
|
|
if ($null -ne $kv.data.NewPlayerDataDic) {
|
|
$modern=$kv.data.NewPlayerDataDic | ConvertFrom-Json
|
|
if ($null -ne $modern.newData) {
|
|
$parts=([string]$modern.newData).Split(',')
|
|
if ($parts.Length -ne 19) { throw 'InvalidModernCoreLength' }
|
|
$levelText=$parts[0]; $source='NewPlayerDataDic.newData[0]'
|
|
}
|
|
}
|
|
$level=0
|
|
if (![int]::TryParse($levelText,[ref]$level) -or $level -lt 1) { throw 'MissingActualKVLevel' }
|
|
if ($null -ne $kv.data.Lv -and [int]$kv.data.Lv -ne $level) { throw 'ConflictingKVLevel' }
|
|
$name=[string]$kv.data.DisplayName
|
|
if ([string]::IsNullOrEmpty($name)) { $name='Angler#'+$id }
|
|
$profiles[$id]=@{PlayFabId=$id;DisplayName=$name;AvatarUrl=[string]$kv.data.AvatarUrl;Level=$level}
|
|
$report.kvInputs += @{ account=$id; level=$level; levelSource=$source; displayNamePresent=![string]::IsNullOrEmpty([string]$kv.data.DisplayName); avatarPresent=![string]::IsNullOrEmpty([string]$kv.data.AvatarUrl); buildingPresent=($null -ne $kv.data.InfiniteBuildingData); lastLoginTimeUtc=$kv.data.LastLoginTimeUtc }
|
|
$before[$id]=Describe-Friends (Send-Original $id 'friend/GetFriendsList' @{})
|
|
}
|
|
if (!((Has-Friend $before['101'] '102') -and (Has-Friend $before['102'] '101'))) {
|
|
$add=Send-Original '101' 'friend/AddFriend' @{MySelf=$profiles['101'];MyFriendCount=$before['101'].count;FriendPlayFabId='102'} $true
|
|
if ($null -eq $add.code -or $add.code -notin @(1,2)) { throw 'AddFriendBusinessFailure' }
|
|
$forward=Describe-Friends (Send-Original '101' 'friend/GetFriendsList' @{})
|
|
$reverse=Describe-Friends (Send-Original '102' 'friend/GetFriendsList' @{})
|
|
if (!((Has-Friend $forward '102') -and (Has-Friend $reverse '101'))) {
|
|
$accept=Send-Original '102' 'friend/AcceptFriend' @{Status=1;PlayFabId='102';FriendPlayFabId='101'} $true
|
|
if ($null -eq $accept.code -or $accept.code -ne 1) { throw 'AcceptFriendBusinessFailure' }
|
|
}
|
|
}
|
|
} catch { $report.error=$_.Exception.Message }
|
|
finally {
|
|
foreach ($id in @('101','102')) {
|
|
if ($before.ContainsKey($id)) {
|
|
try { $after[$id]=Describe-Friends (Send-Original $id 'friend/GetFriendsList' @{}) }
|
|
catch { if (!$report.error) { $report.error=$_.Exception.GetType().Name } }
|
|
}
|
|
}
|
|
$report.mutual=($after.ContainsKey('101') -and $after.ContainsKey('102') -and (Has-Friend $after['101'] '102') -and (Has-Friend $after['102'] '101'))
|
|
Save-Evidence
|
|
$sessions.Clear(); $profiles.Clear(); $auth=$null; $kv=$null
|
|
}
|
|
$report | ConvertTo-Json -Depth 16
|