Files
ft/CodexOutput/upserver/B3BatchMigration/M2/M2-四项PlayFab能力迁移-服务端开发对接文档-2026-09-30.md
Liubing\LB 6813da149a feat(upserver): 保存M2客户端KV迁移至本人统计写检查点
保存独立KV用户服务、固定会话存档与原业务接线,以及C3d资料缓存和C3e本人统计写。
保留原PF/AGG路线、业务公式和时序;榜单读取、好友资料、公告、匹配及首场景闸仍待完成。

验证:ServiceHost两条件各33组646断言;Unity CLI Loop编译0错误269警告。
包含相关方案、评审裁决、测试及状态文档。未操作服务器、正式账号或打包。
2026-09-30 19:19:05 +08:00

345 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 四项PlayFab能力迁移:服务端开发对接文档
日期:2026-09-30。用途:交给当前线上服务器开发者,实施等级榜、好友及公开资料、公告、匹配的KV/自有服务器替代能力。
**目标是平移现有功能,保留客户端业务规则。** LevelB不重新设计,原客户端入口、计算、更新时机、显示、异常和匹配流程保持;替换PlayFab提供方及身份。本文请求/响应示例是拟议合同,不表示线上已部署。已有等价接口优先复用;路由或字段已有规范时,服务端给出映射即可,不必另建一套接口。好友现有路由的兼容字段不得随意变更。
本文件汇总当前交付要求,可以独立交给服务端阅读;附录旧文档仅供追溯。客户端Agent未修改服务器代码;客户端新增接线仍待C3c完整审核通过。服务器内部存储、索引和队列实现由服务器开发者负责,不要求引用PlayFab SDK。
此前清单中的“公告/匹配尚未拟定替代合同”更新为本文已有提案;对接时以本文为当前完整输入,不把历史待办当作尚未完成的文档工作。新提案与实际线上部署仍分别核对。
## 1. 总览:原调用与拟替代方式
| 能力 | 原PlayFab调用 | 原客户端位置(相对Client/Assets/Scripts) | 拟替代方式 |
|---|---|---|---|
| 等级榜统计写入 | PlayFabClientAsyncAPI.UpdatePlayerStatisticsAsync | DataCenter/DataCenter.LeadboardData.cs:158 | 本人原统计名/值经KV管理器存本人KV;属于既定存储接线,不新增业务方案 |
| 全球/地区等级榜读取 | PlayFabClientAsyncAPI.GetLeaderboardAsync | 同文件:171 | 服务器内部读取对应KV统计并返回原榜单结果;缺等价入口时拟POST leaderboard/GetLeaderboard |
| 好友列表及其资料 | PlayFabClientAsyncAPI.GetFriendsListAsync | Services/FriendService.cs:132 | 复用POST friend/GetFriendsList,一次返回好友关系和KV公开资料 |
| 他人昵称/头像、普通等级 | GetPlayerProfileAsync;GetUserDataAsync(Keys=["Lv"], PlayFabId=目标ID) | Services/UserService.cs:810、849、874 | 优先扩展原POST friend/SearchFriend公开投影,由KVUserService消费;不直接跨账号读私有KV |
| 游戏公告 | PlayFabClientAsyncAPI.GetTitleNewsAsync | Services/TileNewsManager.cs:24 | 服务器提供游戏级公告读取;缺等价入口时拟POST commondata/GetTitleNews |
| 多人匹配 | PlayFabMultiplayerAPI.CreateMatchmakingTicket / GetMatchmakingTicket / CancelMatchmakingTicket / GetMatch | Duel/UI/EventFishingDuelPanel.cs:608–825 | 服务器承接原四步票据流程;拟matchmaking下四个同名动作,规则和客户端时序保持 |
四类功能不是都能用“读本人KV”完成:个人统计保存在KV;跨玩家资料/榜单由服务器内部取KV后返回;公告是游戏级内容;匹配由服务器协调玩家。不能用普通键值读写冒充匹配,也不要求客户端取得他人完整存档。
## 2. 通用合同与已知事实
- 固定基址:`http://s101.local:18080/`。HTTP示例路径相对于此基址;JSON请求使用Content-Type: application/json,沿现有登录Cookie认证。
- 玩家唯一身份使用登录返回的PlayerId,即UserID,按字符串传输,不当整数解析。旧playFabId/PlayFabId字段名暂时兼容保留,值传新UserID;字段统一改名另行处理。
- 本人playerdata/Get/Set/Delete由认证会话确定账号,不能带目标ID读取他人。跨玩家查询由服务端内部按业务授权读取,仅返回下文公开字段。
- 新拟接口成功使用HTTP200、code=1、msg="";失败code=0并给非敏感错误类别。401/403沿现有认证处理;参数错误建议400,服务/存储异常建议500。既有friend接口的业务码保持,不强制改为新统一码。
- 缺键、确认不存在、请求失败必须可区分;读取失败不能包装为成功空列表或默认数据。时间采用UTC ISO 8601往返格式,整数与JSON字符串类型按示例保留。
- 新接口通过现有CustomServerMgr的同会话传输接线;原PF模式仍使用原SDK,KV模式不能再借PF读取缺少的数据。
**当前证据**:正式101/102账号已互加好友,friend/GetFriendsList与SearchFriend成功且返回新ID。好友项原格式为JSON字符串数组,字段PlayFabId/DisplayName/AvatarUrl/Level;搜索info为对象,字段playFabId/displayName/avatarUrl/level。当时level均1,本人KV现代档等级为62/60;未执行UpdateProfile,不能推断服务器内部原因。样例未返回LevelB/登录时间,本人KV当时也未取得这两个键。此为历史测试结果,不代表当前部署或全服数据状态。
## 3. 等级榜:沿用原数据,只替换提供方
### 3.1 原调用与写入边界
SetLvData仍由客户端在原时点计算:`lv * 100000 + InfiniteBuildingLevelInt`,保留原TryParse、建筑上限99999、地图等级上限及300秒延迟更新路径。原来一次UpdatePlayerStatistics请求包含:
```json
{"Statistics":[{"StatisticName":"LevelB","Value":1200034},{"StatisticName":"ASLevelB","Value":1200034}]}
```
等级12、建筑34对应1200034。地区统计名为ContinentCode+LevelB;有效地区AF/AN/AS/EU/NA/OC/SA,地区缺失沿已审边界,不虚构地区或把全球榜当地区榜。
迁移后的本人KV线格式沿现有playerdata/Set:
```json
{"data":{"LevelB":"1200034","ASLevelB":"1200034"}}
```
这只是原统计名称/值的存储适配:两个键同值,十进制Int32字符串。客户端仍经KV管理器/原存档句柄,不另建HTTP存档路径、不刷新无关待写数据。不能把它解释为重新设计LevelB,也不能让服务器查询时按当前lv/建筑重新算分。既有KV数据直接使用;PF旧统计是否已迁入KV需核实,不能把样例缺键当成允许初始化全服数据或删除旧数据。
### 3.2 原读取方式
GetLeadboardData构造GetLeaderboardRequest:StatisticName为全球或地区名,StartPosition=0,MaxResultsCount默认50,ProfileConstraints要求头像、昵称、Locations。返回GetLeaderboardResult.Leaderboard。
消费者使用PlayFabId、Position、StatValue、Profile.DisplayName/AvatarUrl/LastLogin。Position从0起,UI排名Position+1;分数除以100000/取模显示等级与建筑。当前该消费链不读Locations。空榜时原客户端不刷新缓存/不发事件,保持原条件。
### 3.3 拟读取合同
优先使用已有KV等级榜等价接口;若无,采用`POST /leaderboard/GetLeaderboard`:
```json
{"statisticName":"LevelB","startPosition":0,"maxResultsCount":50}
```
statisticName仅允许LevelB及上述地区统计名,起始位置非负,拟数量范围1–100(当前调用50)。地区榜只换statisticName,不另增地区判断。无有效统计名/范围应失败,不静默改为全球榜。
```json
{
"code":1,"msg":"",
"leaderboard":[
{"playFabId":"102","position":0,"statValue":1200034,
"profile":{"displayName":"Angler#102","avatarUrl":"","lastLogin":"2026-09-30T01:00:00.0000000Z"}}
]
}
```
| 字段 | 要求 |
|---|---|
| playFabId | 新UserID,非旧PF ID |
| position | 全榜零基位置;分页不能每页归零 |
| statValue | 对应已存KV统计的Int32值,不能填普通等级 |
| profile | 必须是对象;昵称/头像来自该玩家KV公开资料,缺键可null |
| profile.lastLogin | 来自LastLoginTimeUtc,缺键null;不因为缺时间过滤榜单项 |
成功无记录/起点超过榜尾返回`{"code":1,"msg":"","leaderboard":[]}`。不存在所选统计键的玩家不生成该榜条目;有键但格式损坏、聚合读取失败不伪装空榜,建议HTTP500、code=0、leaderboard=null、msg=KV_STAT_DATA_INVALID或KV_LEADERBOARD_READ_FAILED。
服务器读取已存统计,按原榜单规则返回;旧PF后台同分次序、统计更新聚合方式和重置配置不在客户端源码中,服务端需复用已有规则或对照原配置,不能自行改成按ID打破同分、每次重算、取最大值或新增周期清零。本要求是等价性核验,不是重新设计统计业务。已有event/GetLeaderboards属于活动榜,不能直接代替此榜。
## 4. 好友及他人公开资料:扩展原业务接口
### 4.1 原好友调用
GetFriendsListAsync请求ProfileConstraints:ShowAvatarUrl/ShowDisplayName/ShowStatistics/ShowLastLogin=true。消费者从Profile.Statistics找Name=LevelB,读取Profile.LastLogin;本人排除、默认名、排序和事件由原客户端完成。
原过滤只有Profile==null或Statistics==null;Statistics为空数组时好友保留、分数为0。缺LastLogin不自动过滤好友,下游原Nullable.Value路径有异常风险,不能通过假时间或额外过滤声称迁移通过。
### 4.2 拟复用POST /friend/GetFriendsList
请求仍`{}`,服务器取认证本人原好友关系,一次批量投影好友KV公开资料。不修改增删好友流程,不增加客户端逐个取档请求。
外层code/msg/friends和friends中JSON字符串项保持;原四字段保持兼容含义,新增Profile供替代原PF消费者:
```json
{
"code":1,"msg":"",
"friends":[
"{\"PlayFabId\":\"102\",\"DisplayName\":\"Angler#102\",\"AvatarUrl\":\"\",\"Level\":12,\"Profile\":{\"DisplayName\":\"Angler#102\",\"AvatarUrl\":\"\",\"Statistics\":[{\"Name\":\"LevelB\",\"Value\":1200034}],\"LastLogin\":\"2026-09-30T01:00:00.0000000Z\"}}"
]
}
```
| 新增公开字段 | 来源/缺值规则 |
|---|---|
| Profile.DisplayName / AvatarUrl | 对方KV同名键;缺键null,原客户端默认名处理保留 |
| Profile.Statistics | 投影对方LevelB;Value为Int32,缺LevelB键为[],不以普通Level替代 |
| Profile.LastLogin | 对方LastLoginTimeUtc;UTC字符串,缺键null |
| Profile | 资料实体确认不存在才为null;关系项保留。读取失败不能当不存在 |
Profile与其Statistics字段必须出现;明确不存在统计集合可Statistics=null,对应原跳过语义,不能与正常KV读取后缺LevelB的[]混淆。无好友返回friends=[]。部分好友KV读取失败、存在但损坏的统计/时间建议整次HTTP500/code=0/friends=null,避免假空列表或部分成功掩盖数据问题。原业务失败码保持;旧服务器未提供Profile是合同未部署,不能当全部好友资料为空。
LastLoginTimeUtc当前由本人每日初始化后沿已有流程写入,不等于所有认证成功时刻;本阶段按用户既定“先存KV”方向读取该值,联调对照真实时点,不擅自改本人跨日流程。外层Level仍为普通等级,不改塞复合分数。
### 4.3 原他人资料调用与拟复用SearchFriend
- GetPlayInfo原先同步返回缓存中的同一占位对象,再异步GetPlayerProfileAsync取得昵称/头像,更新对象并发布GetClubPlayInfoEvent。
- GetPlayLv原先GetUserDataAsync指定目标ID和Keys=["Lv"]。成功但缺Lv键时原默认1;错误路径和原缓存流程不在本次修正。
- 这两个客户端入口保留,在KVUserService中替换数据源。SearchFriend已存在且新UserID可用,不另造公开资料查询系统。
沿现有`POST /friend/SearchFriend`及原请求字段:
```json
{"PlayFabId":"102"}
```
**拟最小兼容扩展**:保留code/msg/info与info原字段;在info增加kvProfile,明确其是目标KV的公开投影,以免把旧缓存的level=1误当完整迁移成功。已有服务能保证info本身就是同义KV公开投影时,可省去新增kvProfile,由服务端在交付中明确源数据和缺值映射;两者择一形成最终合同,客户端不运行时猜测两套来源。
```json
{
"code":1,"msg":"",
"info":{
"playFabId":"102","displayName":"Angler#102","avatarUrl":"","level":12,
"kvProfile":{"displayName":"Angler#102","avatarUrl":"","level":12}
}
}
```
kvProfile昵称/头像来源同好友投影;level是**普通等级**,按现有KV存档布局和加载优先级取得,不能使用LevelB替代。当前现代档为NewPlayerDataDic.newData第0槽(19槽格式),旧格式按已存在加载规则处理;不得仅查独立Lv键就忽略已有现代档,也不为此新增等级真值副本。数据格式损坏应失败,不能静默返回1。
建议缺值合同:目标账号/公开资料实体确认不存在时kvProfile=null;资料存在但确无等级数据时level=null,由客户端沿原“成功缺Lv键默认1”映射;昵称/头像缺键null。已存在等级返回整数。旧搜索行为若返回Unknown占位,保留原info行为,新增kvProfile明确null,不能让占位冒充实际资料。现有账号的成功样例不能证明不存在/失败语义,服务端需回传这两类样例完成对照。HTTP/业务失败保留失败,不转为成功null。
## 5. 公告读取:原游戏级内容迁到服务器
### 5.1 原调用方式和依据
`GetTitleNewsAsync(new GetTitleNewsRequest())`为空请求;PlayFabSettings.TitleId由UserService.cs:339配置,表示游戏作用域。没有按玩家ID、等级、地区筛选。项目内SDK模型说明Count未设置默认10;原客户端未显式设置Count。
原返回News项包含NewsId、Title、Body、Timestamp。客户端只取News[0],界面显示标题、正文、日期;用Timestamp与本地PlayerPrefs已读标记及账号CreateTime比较决定红点。客户端没有对返回列表重新排序。
### 5.2 拟POST /commondata/GetTitleNews
如已有游戏级公告接口则复用。拟新入口沿现有同会话CommonDataRequest,无筛选参数:
```json
{}
```
```json
{
"code":1,"msg":"",
"news":[
{"newsId":"notice-20260930-01","title":"公告示例","body":"此处为公告正文。","timestamp":"2026-09-30T01:00:00.0000000Z"}
]
}
```
- 服务器从当前游戏/环境的全局公告数据源返回已发布公告,不能按账号返回不同私有存档;无需客户端再传PF TitleId。多游戏部署由服务端沿既有可信游戏上下文区分,不能将全局作用域意外扩大到其他游戏。
- 内容可放在已有服务器公告存储,或服务端专用全局KV集合;不强制新建库/后台管理系统,不把公告写入每个玩家KV。发布入口复用现有运维方式,玩家只读接口无发布权限。
- 原NewsId、标题、正文、发布时间和发布顺序整体迁移,不能为了迁移把旧公告时间改成“现在”,否则会改变红点和已读状态。
- 拟返回最近10条已发布公告,与原默认数量对应;列表第一项须与原PF可见首项一致。通常按发布时间倒序,若原发布系统有同时间排序/优先级,则沿原顺序,不自行另定同时间ID排序。排序在服务端完成,客户端仍直接取第一条。
- newsId/title/body为字符串,timestamp为有效UTC时间;空标题/正文若原内容允许则原样保留,不加内容修订。缺少必要字段或损坏时间是数据错误,不能伪造发布时间。
- 无公告:HTTP200/code=1/news=[]。获取失败:建议HTTP500/code=0/news=null/msg=TITLE_NEWS_READ_FAILED。客户端按原PF失败null路径处理News和TaskCompletionSource,不在本轮修复原异步UI行为。
**服务端交付要点**:已有公告数据是否已迁移、实际作用域、原首条一致样例、UTC时间保留情况。具体线上公告正文未在本次读取,文中是合成示例,不能作为正式内容发布。
## 6. 匹配:服务器承接原四步,不重做对战流程
### 6.1 原调用与输入
队列名固定`DuelPVP`(FishingDuelManager_Network.cs:18),GiveUpAfterSeconds取eventSoloConfig.MaxMatchTime,不在替代接口硬编码新时长。原CreateMatchmakingTicketRequest.Creator.Entity是PF EntityKey;迁移后服务器从认证会话取UserID,不要求客户端伪造EntityKey。
原Attributes.DataObject内容:
| 字段 | 原类型与来源 | 迁移要求 |
|---|---|---|
| Tier | string[],FDM.eventSolomain.MatchTierList转字符串 | 原匹配条件输入原样传递 |
| Map | string[],FDM.mapIDs转字符串 | 原候选地图原样传递 |
| Group | string[],FDM.GetGroups() | 原分组原样传递 |
| Details | JSON字符串,不是嵌套对象 | 原GetDetails输出,含ID/Name/Avatar/RandScore/Mag |
| Details.ID | string,IUserService.UserId | 必须对应认证UserID |
| Details.RandScore / Mag | int,duelData.progress / magnification | 原数值,不自行增加匹配过滤条件 |
原查询每3秒一次,EscapeObject=false;结果查询ReturnMemberAttributes=true。原匹配失败由既有MachingEnd(null)进入原后续流程;不能把当前KV分支一进入就结束的开发冻结当成正常迁移结果。
### 6.2 四个拟议动作
已有服务器等价匹配服务优先复用,下列路径仅在缺入口时采用。所有票据操作按认证账号核所有权,匹配结果仅参与者可读;TicketId/MatchId作为不透明字符串,不能用客户端任意目标ID访问他人票据。
| 动作 | 拟HTTP入口 | 请求 | 成功关键输出 |
|---|---|---|---|
| 创建 | POST /matchmaking/CreateMatchmakingTicket | queueName、giveUpAfterSeconds、attributes | ticketId |
| 查询 | POST /matchmaking/GetMatchmakingTicket | queueName、ticketId | status、matchId、cancellationReasonString |
| 取消 | POST /matchmaking/CancelMatchmakingTicket | queueName、ticketId | code=1确认处理,已有终态按实际合同返回 |
| 结果 | POST /matchmaking/GetMatch | queueName、matchId | matchId、members及每位成员原attributes |
### 6.3 创建示例
示例数值仅用于说明类型,实际从客户端原配置/状态传入:
```json
{
"queueName":"DuelPVP","giveUpAfterSeconds":30,
"attributes":{
"Tier":["1","2"],"Map":["101","102"],"Group":["0"],
"Details":"{\"ID\":\"101\",\"Name\":\"Angler#101\",\"Avatar\":\"\",\"RandScore\":100,\"Mag\":1}"
}
}
```
```json
{"code":1,"msg":"","ticketId":"ticket-example-101"}
```
服务器校验Details.ID与认证UserID一致;接受原属性类型、原队列和原超时时间范围,不以默认常量替换请求。原PF队列成员数、Tier/Map/Group规则、放宽条件等需要从现有等价实现或原DuelPVP配置复用,**源码仅证明字段输入,不能凭字段名自行发明匹配算法**。这属于服务端移植所需配置输入,不是要求用户重新设计玩法。
### 6.4 查询示例与状态
```json
{"queueName":"DuelPVP","ticketId":"ticket-example-101"}
```
```json
{"code":1,"msg":"","status":"WaitingForMatch","matchId":null,"cancellationReasonString":null}
```
```json
{"code":1,"msg":"","status":"Matched","matchId":"match-example-01","cancellationReasonString":null}
```
```json
{"code":1,"msg":"","status":"Canceled","matchId":null,"cancellationReasonString":"Requested"}
```
- WaitingForMatch为本协议拟用的等待状态,若既有服务还有原有非终态,可按同义状态返回;客户端原来只对Matched/Canceled作终态分支,其余继续原轮询。
- Matched必须同时给有效MatchId并能够读取该次结果;双方取得同一MatchId和同一成员集合,不能在每次查询新建结果。
- Canceled给真实原因。Requested表示用户取消;达到GiveUpAfterSeconds的超时也需映射到原可识别Canceled并说明超时原因,不能永久保持等待。具体原PF原因字符串从原配置/响应对照保留,不用示例覆盖所有原因。
- ticket不存在/过期读取/请求失败不得包装成Matched;服务端返回明确错误,客户端进入原查询失败路径。
### 6.5 取消
请求同查询的queueName/ticketId。等待中的取消成功示例:
```json
{"code":1,"msg":""}
```
服务端必须处理取消与成对成功的竞争,终态要与后续查询一致;不能既向一方报告已取消成功又把其作为新匹配结果交给另一方。已取消可返回成功,已匹配时拟HTTP409/code=0/msg=MATCH_ALREADY_COMPLETED;此处为待审错误映射,若既有等价服务或原PF终态合同不同,应提供证据调整,不能宣称该错误码已经线上存在。
客户端沿原逻辑先隐藏按钮、清currentTicketId再发送取消,成功只记录,失败只记错。本次不把取消改为“等服务器确认后才能退出”,不顺便修竞态或新增重试。
### 6.6 获取结果
```json
{"queueName":"DuelPVP","matchId":"match-example-01"}
```
```json
{
"code":1,"msg":"","matchId":"match-example-01",
"members":[
{"playerId":"101","attributes":{"Tier":["1","2"],"Map":["101","102"],"Group":["0"],"Details":"{\"ID\":\"101\",\"Name\":\"Angler#101\",\"Avatar\":\"\",\"RandScore\":100,\"Mag\":1}"}},
{"playerId":"102","attributes":{"Tier":["1","2"],"Map":["102","103"],"Group":["0"],"Details":"{\"ID\":\"102\",\"Name\":\"Angler#102\",\"Avatar\":\"\",\"RandScore\":100,\"Mag\":1}"}}
]
}
```
成员playerId为新UserID;attributes是对象,Details仍为原JSON字符串。Map仍可按原消费者转int[],Tier转string[]。服务端保证成员ID与Details.ID一致,结果是本次票据提交的属性快照,不在查询时改成另一时刻的玩家数据。
原消费者遍历成员,按本人Entity.Id排除自己,迁移时仅换成playerId对比本人UserID;随后解析对方Details/Map,清currentTicketId,调用FDM.MachingEnd(matchId, details, mapArray),再启动原5秒Ready检查。缺成员/坏属性按原失败/解析处理,不创造假对手填补。
**后续流程约束**:FishingDuelManager_Network.cs:102的MachingEnd会取双方Map交集并以matchId.GetHashCode作为原种子输入,所以服务端不能更换MatchId或配出不符合原规则的无共同地图组合;本次不改变种子算法。对战继续使用原RT通信,enemyId等使用同一UserID。匹配服务不新增对战服务器分配、战斗结算或奖励逻辑。GetHashCode跨运行环境的旧风险只记录,不在本迁移改算法。
### 6.7 错误响应
新拟匹配接口的失败体统一保留code/msg,不返回假成功字段,例如:
```json
{"code":0,"msg":"MATCHMAKING_UNAVAILABLE"}
```
参数/属性格式错误400、无权读取403、不存在票据/结果404、上述终态冲突409、服务错误500;认证沿原401/403。服务器仅在规则允许时产生终态,存储错误不冒充正常Canceled。客户端映射原相应SDK错误回调:创建失败FDM.MachingEnd(null),查询/结果失败MachingEnd(),取消失败记日志;不改变现有用户流程。
## 7. 服务端交付与联调验收
请一次交付下列内容,避免逐接口往返询问。已有实现填写实际映射;需要补实现按本拟合同对照。无需提供密码、Cookie、完整玩家私有档。
| 交付项 | 要求 |
|---|---|
| 接口对应表 | 本文拟路径 → 实际方法/路径;标明已有可用、待扩字段、需新增、已部署版本 |
| 请求与响应 | 各项成功、空/缺失、失败的脱敏JSON;新增字段精确大小写/类型,保持旧客户端兼容 |
| 数据来源 | 等级榜/好友/公开资料取哪些已存KV字段与布局;公告游戏作用域与发布来源;匹配队列配置来源 |
| 等价配置证据 | 原LevelB统计同分/更新/重置及DuelPVP匹配规则沿用情况;未取得配置处明确列出,不自行新定业务 |
| 数据准备情况 | 原统计/公告是否已迁移、测试账号是否有对应字段;缺档不补假数据,不用查询时刻充当登录时间 |
| 联调信息 | 固定正式地址对应版本、可用时间、必要服务器日志定位方式及已知差异;无需更换环境地址 |
验收按原行为执行:
1. **榜单**:全球/地区、0/1/50/超过50条、同分、缺统计键/损坏统计、写后读取;位置/分数/UI解码与旧流程一致。
2. **好友/公开资料**:101与102互查身份正确;好友一次返回资料、LevelB与本人KV一致、时间来自已存值;Profile/Statistics缺失和空数组分开;普通等级与现代KV档一致;旧搜索消费者兼容。
3. **公告**:同游戏两账号读到同一内容;原首条与时间保留;新/旧账号的原红点判断、已读标记、无公告和请求失败保持。不要发布示例正文作测试。
4. **匹配**:两个真实或隔离测试玩家创建→等待→匹配→同一结果;用户取消、超时、非法票据、服务失败及终态竞争;属性和共同地图有效;同UserID进入原RT与5秒Ready链路。不能只验证四个HTTP200就称匹配通关。
5. **客户端回归**:KV分支上述PF调用为0;PF/AGG原路线保留;不改变LevelB算法、原空榜行为、好友过滤、公告首项、匹配3秒轮询/原失败处理。所有开发冻结在完整通关前恢复。
账号101/102可用于已授权必要测试,103–110仅按需要使用,不扩展正式客户端准入名单。正式测试不能擅改等级/奖励/付费数据来制造样例;协议夹具与线上实测分开记录。客户端Unity只用CLI Loop,不打包。
## 8. 责任与审核边界
- 服务端开发:在其负责的当前线上源码实现/复用上述接口、内部KV读取/聚合、游戏公告读取及匹配协调;保留已有接口兼容。本文不要求修改当前电脑上的旧服务器副本。
- 客户端开发:在现有LeadboardData、FriendService/FriendModel、KVUserService、TileNewsManager、EventFishingDuelPanel中完成必要接线与结果映射,保留原流程。IPlayerSaveStore本人统计写是存储替换,不是新业务设计。
- 审核:四类完整合同、原行为映射与客户端最小文件/方法边界合并现有C3c完整审核;通过后连续实施,不另拆LevelB、公告或匹配逐方法审核。当前文档是开发对接提案,非审核PASS或部署证明。
本轮文档已核对本地客户端代码和项目内SDK模型;没有访问PlayFab后台或当前服务端配置,没有实施生产代码/测试代码修改、线上写入或打包。
## 附录:既有材料
- [当前待设计与接线清单](M2-C3c当前待设计与接线清单-2026-09-30.md)
- [LevelB只做存储平移的范围纠正](M2-等级榜设计范围纠正-LevelB沿用原逻辑-2026-09-30.md)
- [好友KV公开资料详细草案](M2-好友KV资料服务端接口设计-待完整审核-2026-09-30.md)
- [等级榜读取详细草案](M2-等级榜读取与本人统计键接口设计-待完整审核-2026-09-30.md)
本文新增的公开资料kvProfile、公告和匹配HTTP合同均为拟议设计;旧接口已有等价能力可用实际映射替代,服务端实施反馈与同次完整审核明确最终版本,不让客户端靠多重开关猜协议。