# 17 - 服务端迁移交接文档 > 面向对象:新加入的服务端/运维同事。 > 目标:说明 TH1 当前服务器架构、使用的阿里云服务、各服务怎么协作,以及完整迁移时需要迁移和验证什么。 > 更新时间:2026-06-30。 > 事实来源:`Tools/OSS/game-upload-function/`、`Unity/Assets/Scripts/TH1_Logic/Oss/`、`MD/GameMDFramework/15-服务端-GameUploadFunction.md`。 --- ## 1. 当前服务端定位 TH1 目前没有传统意义上的常驻游戏逻辑服务器。回合逻辑、联机同步和战斗结算主要在 Unity 客户端侧完成,联机链路依赖 Steam 相关能力。当前阿里云后端的核心职责是: - 验证 Steam 玩家身份。 - 为客户端签发有限权限的 OSS 上传凭证。 - 让客户端把对局数据、统计数据、玩家问题汇报、问卷答卷等文件直接上传到 OSS。 - 使用 Tablestore 做短时缓存,减少 Steam Web API 和 STS 调用次数。 - 提供开发者本地工具从 OSS 拉取数据并查看。 因此,迁移重点不是迁移一套业务 API 集群,而是迁移“函数计算 + OSS + STS/RAM + Tablestore + 硬编码客户端入口”的上传基础设施。 --- ## 2. 一页架构图 ```mermaid flowchart TD Unity["Unity 客户端
OssManager / StsTokenService"] -->|POST JSON
steamId, steamAppId, authTicket, type, version| FC["阿里云函数计算
game-upload-function
Node.js, port 9000"] FC -->|AuthenticateUserTicket| Steam["Steam Web API"] FC -->|getRow / putRow| OTS["阿里云 Tablestore
Players 表"] FC -->|AssumeRole| STS["阿里云 STS / RAM Role"] STS --> FC FC -->|STS + Post Policy
exact objectKey| Unity Unity -->|PostObject multipart/form-data| OSS["阿里云 OSS
Bucket: th1-oss
endpoint: oss-cn-shanghai.aliyuncs.com"] Tools["Unity 编辑器工具 / Python 查看器"] -->|ListObjects / GetObject| OSS ``` 关键点: - Function Compute 不接收游戏数据文件,只返回上传凭证。 - 文件上传是 `Unity -> OSS` 直传。 - STS 和 Post Policy 都限制到一个精确 `objectKey`,不是给客户端开放整个 Bucket 写权限。 - Tablestore 的数据主要是短时缓存,不是永久业务数据源。 --- ## 3. 使用的阿里云服务 | 服务 | 当前用途 | 迁移时要做什么 | |------|----------|----------------| | 函数计算 Function Compute | 承载 `game-upload-function` HTTP 服务。公开入口当前硬编码在 Unity:`https://get-sts-token-qltjykaafr.cn-shanghai.fcapp.run`。 | 新建或迁移函数应用,Node.js 运行时需支持 >=18,启动 `index.js`,监听端口 9000。若 URL 变化,要同步客户端代码或通过域名/代理保持老 URL 可用。 | | OSS 对象存储 | 存放玩家上传文件。当前默认 Endpoint 是 `oss-cn-shanghai.aliyuncs.com`,工具默认 Bucket 是 `th1-oss`,服务端实际 Bucket 来自 `BUCKET_NAME`。 | 创建目标 Bucket,复制全部对象,保持 objectKey 不变。重点前缀包括版本根目录、`common/`、`collect/`、`bugreport/`、`multilingualreport/`、`questionnaire/`。 | | STS | 函数调用 `AssumeRole` 获取临时凭证。临时凭证有效期 900 秒。 | 创建/迁移 RAM Role,允许被函数使用的 RAM 身份扮演;Role 本身需要允许对目标 Bucket 执行 `oss:PutObject`,函数再用 inline policy 缩到单个 objectKey。 | | RAM | 保存函数调用 STS、Tablestore 的 AccessKey 身份,以及被 STS 扮演的 Role。 | 重建最小权限 RAM 用户/角色策略。不要把 AccessKey、Secret、Steam API Key 写入仓库或文档。 | | Tablestore | `Players` 表,主键 `PlayerId`。缓存 Steam 身份和 STS 上传凭证。 | 创建实例和 `Players` 表。缓存都是分钟级,通常可以不迁移历史行,空表上线只会让首次请求重新验证/签发。 | 当前未看到 ECS、RDS、Redis、CDN、独立 API Gateway 作为这条链路的必需依赖。 --- ## 4. 代码和文档入口 服务端源码: - `Tools/OSS/game-upload-function/index.js`:后端事实来源,包含接口、缓存、Steam 验证、STS 签发、OSS Post Policy。 - `Tools/OSS/game-upload-function/package.json`:Node 依赖和运行时要求。 - `Tools/OSS/game-upload-function/README.md`:函数服务说明。 架构文档: - `MD/GameMDFramework/15-服务端-GameUploadFunction.md`:接口和流程的详细说明。 - 本文档:迁移和交接总览。 Unity 运行时客户端: - `Unity/Assets/Scripts/TH1_Logic/Oss/OssManager.cs`:上传入口、Steam 预校验调度、各上传类型调用。 - `Unity/Assets/Scripts/TH1_Logic/Oss/StsTokenService.cs`:向函数计算请求 STS 凭证。 - `Unity/Assets/Scripts/TH1_Logic/Oss/OssUploadService.cs`:用 PostObject 直传 OSS。 - `Unity/Assets/Scripts/TH1_Logic/Oss/PlayerBugReportService.cs`:玩家 Bug 汇报 zip 打包。 开发者工具: - `Unity/Assets/Scripts/TH1_Logic/Editor/OssEditorWindow.cs`:Unity 编辑器内 OSS 拉取、上传测试、统计导出入口。 - `Tools/PlayerBugViewer/`:拉取和查看 `bugreport/`。 - `Tools/PlayerMultilingualReportViewer/`:拉取和查看 `multilingualreport/`。 - `Tools/PlayerQuestionnaireViewer/`:拉取和查看 `questionnaire/`。 --- ## 5. Function Compute 服务 当前服务是一个 Node.js CommonJS HTTP 服务: - 入口文件:`Tools/OSS/game-upload-function/index.js` - 监听端口:`9000` - Node 版本:`>=18.0.0` - 依赖: - `@alicloud/pop-core`:调用 STS `AssumeRole` - `tablestore`:读写 Tablestore 函数接收 `POST /` JSON 请求,最大请求体 1024 字节。`OPTIONS` 用于 CORS 预检,其他非 POST 请求返回 405。 ### 5.1 必需环境变量 这些值必须从阿里云/Steam 控制台配置到函数计算环境变量,不得提交到仓库: | 变量 | 用途 | |------|------| | `ACCESS_KEY_ID` | 函数使用的 RAM AccessKey ID。 | | `ACCESS_KEY_SECRET` | 函数使用的 RAM AccessKey Secret。 | | `ROLE_ARN` | STS `AssumeRole` 的目标 RAM Role ARN。 | | `BUCKET_NAME` | 目标 OSS Bucket。 | | `STEAM_API_KEY` | Steam Web API Key。 | | `OTS_ENDPOINT` | Tablestore Endpoint。 | | `OTS_INSTANCE` | Tablestore 实例名。 | 兼容/可选变量: | 变量 | 用途 | |------|------| | `STEAM_APP_ID` | 老客户端未传 `steamAppId` 时的默认 AppID。生产环境建议显式配置。 | | `STEAM_ALLOWED_APP_IDS` 或 `STEAM_APP_IDS` | 允许的 Steam AppID 列表。未配置时默认包含 `3774440,3887950`。 | ### 5.2 接口请求字段 | 字段 | 必填 | 说明 | |------|------|------| | `steamId` | 是 | 玩家 Steam ID。 | | `steamAppId` | 否 | 客户端当前 Steam AppID。新客户端会传,老客户端可能不传。 | | `authTicket` | 条件必填 | Steam Auth Ticket。身份缓存未命中时必需。 | | `version` | 否 | 客户端版本。不传时使用 `common` 路径段。 | | `action` | 否 | `steamauth` 表示只做 Steam 身份预校验。 | | `type` | 否 | 上传类型,默认 `ossdata`。 | 上传凭证响应直接映射 Unity `StsCredentials`: ```json { "accessKeyId": "STS.xxx", "accessKeySecret": "xxx", "securityToken": "xxx", "steamAppId": "3774440", "endpoint": "oss-cn-shanghai.aliyuncs.com", "bucket": "th1-oss", "objectKey": "0.7.5/76561198xxx/1780000000000.dat", "policy": "base64...", "signature": "hmac-sha1...", "expiresIn": 900 } ``` --- ## 6. 运行时请求流程 ### 6.1 Steam 预校验 客户端 Steam 就绪后会周期性调用: ```json { "action": "steamauth", "steamId": "...", "steamAppId": "...", "authTicket": "...", "version": "0.7.5" } ``` 服务端流程: 1. 查询 Tablestore 是否已有 `{steamId}#steamauth#{steamAppId}` 身份缓存。 2. 缓存命中则直接返回剩余秒数。 3. 缓存未命中则调用 Steam Web API `ISteamUserAuth/AuthenticateUserTicket/v1/`。 4. 校验通过后写入 Tablestore,缓存 10 分钟。 这个缓存只代表玩家身份近期通过验证,不直接授权 OSS 写入。 ### 6.2 上传凭证签发 客户端请求 `type=ossdata|collectdata|bugreport|multilingualreport|questionnaire`。 服务端流程: 1. 对可缓存类型先查 STS 上传凭证缓存。 2. 如果未命中,检查 Steam 身份缓存。 3. 身份缓存也未命中时,用 `authTicket` 调 Steam Web API。 4. 调阿里云 STS `AssumeRole` 获取临时凭证。 5. 按上传类型和版本生成一个精确 OSS `objectKey`。 6. 用 STS 临时密钥生成 Post Policy 和 HMAC-SHA1 签名。 7. 可缓存类型写回 Tablestore。 8. 返回凭证给 Unity。 9. Unity 使用 multipart/form-data 直接 POST 到 `https://{bucket}.{endpoint}`。 --- ## 7. 上传类型和 OSS 路径 | type | 文件 | 是否缓存 STS | 大小限制 | OSS 路径 | |------|------|--------------|----------|----------| | `ossdata` | `.dat` | 是 | 3 MB | `{version}/{steamId}/{timestamp}.dat` 或 `common/{steamId}/{timestamp}.dat` | | `collectdata` | `.dat` | 是 | 3 MB | `collect/{version}/{steamId}/{timestamp}.dat` 或 `collect/common/{steamId}/{timestamp}.dat` | | `bugreport` | `.zip` | 否 | 10 MB | `bugreport/{version}/{steamId}/{timestamp}-{random}.zip` | | `multilingualreport` | `.zip` | 否 | 1 MB | `multilingualreport/{version}/{steamId}/{timestamp}-{random}.zip` | | `questionnaire` | `.json` | 否 | 512 KB | `questionnaire/{version}/{steamId}/{timestamp}-{random}.json` | 迁移 OSS 时不要只复制显式前缀。`ossdata` 直接放在 Bucket 根部的版本目录下,例如 `0.7.5/{steamId}/{timestamp}.dat`,因此最稳妥策略是整桶复制,保持 objectKey 完全一致。 --- ## 8. Tablestore 数据模型 Tablestore 表: - 表名:`Players` - 主键:`PlayerId`,字符串类型 ### 8.1 Steam 身份缓存 主键: ```text {steamId}#steamauth#{steamAppId} ``` 字段: - `verifiedAt`:验证通过时间,毫秒时间戳。 - `authExpireAt`:身份缓存过期时间,毫秒时间戳。 - `steamAppId`:验证使用的 Steam AppID。 - `version`:客户端版本。 有效期:10 分钟。 ### 8.2 STS 上传凭证缓存 主键: ```text {steamId}#{type}#{steamAppId} ``` 字段: - `accessKeyId` - `accessKeySecret` - `securityToken` - `endpoint` - `bucket` - `objectKey` - `policy` - `signature` - `issuedAt` - `stsExpireAt` - `steamAppId` - `version` 命中条件: - 请求版本和缓存版本一致。 - 签发时间在 5 分钟内。 - STS 剩余有效期大于 2 分钟。 `bugreport`、`multilingualreport`、`questionnaire` 不使用 STS 缓存,因为每次提交必须生成唯一 objectKey,避免覆盖。 迁移建议:Tablestore 只存短时缓存,可以在新环境创建空表。迁移历史缓存意义不大。 --- ## 9. RAM / STS 权限模型 当前函数使用环境变量里的 `ACCESS_KEY_ID` / `ACCESS_KEY_SECRET`: - 调用 Tablestore `getRow` / `putRow`。 - 调用 STS `AssumeRole`。 STS 临时凭证来源于 `ROLE_ARN`,签发时会附带 inline policy: ```json { "Version": "1", "Statement": [ { "Effect": "Allow", "Action": ["oss:PutObject"], "Resource": ["acs:oss:*:*:{BUCKET_NAME}/{objectKey}"] } ] } ``` 迁移时至少需要两层权限: 1. 函数自身使用的 RAM 身份: - 可以 `sts:AssumeRole` 到 `ROLE_ARN`。 - 可以读写目标 Tablestore `Players` 表。 2. 被扮演的 RAM Role: - 基础权限允许对目标 Bucket 执行 `oss:PutObject`。 - 实际下发给客户端的权限由 inline policy 缩小到单个 objectKey。 不要为了省事把客户端 STS 权限放宽到整个 Bucket。当前安全边界依赖“STS exact objectKey + Post Policy exact key + 文件大小限制”共同生效。 --- ## 10. OSS 读写和本地工具 客户端写入: - Unity 先向函数计算拿 `StsCredentials`。 - 再用 `OssUploadService` 直传到 `https://{bucket}.{endpoint}`。 - multipart 字段包括 `key`、`OSSAccessKeyId`、`policy`、`Signature`、`x-oss-security-token`,最后是 `file`。 开发者读取: - Unity `Tools/Oss 编辑器` 可以从 `collect/` 拉取统计数据到 `Tools/OSS/Data/`。 - `Tools/PlayerBugViewer` 从 `bugreport/` 拉取 zip。 - `Tools/PlayerMultilingualReportViewer` 从 `multilingualreport/` 拉取 zip。 - `Tools/PlayerQuestionnaireViewer` 从 `questionnaire/` 拉取 JSON。 这些本地 `Data/` 目录是开发者缓存,不是迁移源。迁移应以 OSS Bucket 为准。 --- ## 11. 客户端硬编码入口 当前函数 URL 在以下代码里硬编码: - `Unity/Assets/Scripts/TH1_Logic/Oss/OssManager.cs` - `Unity/Assets/Scripts/TH1_Logic/Editor/OssEditorWindow.cs` - `Unity/Assets/Scripts/TH1_Logic/Editor/SteamEditorWindow.cs` 当前 URL: ```text https://get-sts-token-qltjykaafr.cn-shanghai.fcapp.run ``` 迁移风险: - 已发布老客户端不会自动知道新函数 URL。 - 如果直接换新的 fcapp.run 地址,老版本上传会失败。 推荐策略: 1. 如果可以,给函数计算接入项目自有域名,并让客户端以后只写自有域名。 2. 迁移期间保留旧函数或让旧 URL 代理到新环境,直到不再需要兼容老客户端。 3. 新版本客户端再修改 `FunctionUrl`,并通过 Steam 上传流程测试器验证。 --- ## 12. 完整迁移步骤 ### 12.1 迁移前盘点 从旧环境记录以下信息,记录时不要把密钥值写入仓库: - Function Compute: - 地域、函数/应用名、运行时、启动命令、端口、HTTP 触发器、公开 URL。 - 环境变量名称和值来源。 - 日志查看位置和错误报警方式。 - OSS: - Bucket 名称、地域、Endpoint。 - 对象总量、总大小、关键前缀。 - 是否配置生命周期、跨域、版本控制、服务端加密。 - RAM / STS: - 函数使用的 RAM 身份。 - `ROLE_ARN` 对应角色。 - 角色信任策略和权限策略。 - Tablestore: - 实例名、Endpoint、表 `Players` 的主键结构。 - Steam: - Steam Web API Key 管理账号。 - 允许的 AppID:当前默认兼容 `3774440`、`3887950`。 - Unity 客户端: - 当前发布版本是否还能接受修改函数 URL。 - 是否需要保留旧 URL 兼容历史版本。 ### 12.2 新环境资源创建 建议优先保持同地域 `cn-shanghai`,减少 Endpoint、延迟和配置差异。 1. 创建 OSS Bucket。 - 名称可以沿用或换新,但如果换新,要同步 `BUCKET_NAME` 和工具默认值。 - Endpoint 目前按 `oss-cn-shanghai.aliyuncs.com` 返回给客户端。 2. 创建 Tablestore 实例。 - 创建 `Players` 表。 - 主键:`PlayerId`,字符串。 3. 创建 RAM 身份和 Role。 - 函数 RAM 身份:允许 `sts:AssumeRole`,允许访问 Tablestore `Players`。 - STS Role:允许 OSS `PutObject` 到目标 Bucket,最终权限由 inline policy 缩小。 4. 创建 Function Compute 应用/函数。 - 上传 `Tools/OSS/game-upload-function/` 代码包。 - 安装依赖或随包部署 `node_modules`,以目标平台部署规范为准。 - 启动 `node index.js`。 - 监听端口 9000。 - 配置 HTTP 触发器。 5. 配置函数环境变量。 - 填入第 5.1 节列出的必需变量。 - 显式配置 `STEAM_APP_ID` 和允许 AppID 列表,避免老客户端行为不确定。 ### 12.3 OSS 数据迁移 必须保持 objectKey 不变。迁移对象包括: - `0.x.x/{steamId}/{timestamp}.dat` 等版本根目录。 - `common/` - `collect/` - `bugreport/` - `multilingualreport/` - `questionnaire/` 推荐做法: - 使用阿里云官方迁移工具或 `ossutil` 整桶复制。 - 复制后按前缀抽样比对对象数量和文件大小。 - 不要通过 Git 或本地 `Tools/OSS/Data` 反向恢复 OSS;本地目录只是缓存。 ### 12.4 Tablestore 迁移 通常不迁移旧缓存: - Steam 身份缓存只有 10 分钟。 - STS 上传凭证缓存最多复用 5 分钟,STS 自身 15 分钟过期。 - 空表上线后,第一次请求会重新走 Steam 验证和 STS 签发。 只有在做无感切换且非常在意首次请求延迟时,才考虑导出导入缓存。即便不迁移,功能正确性不受影响。 ### 12.5 切流 优先选择可回滚方案: 1. 新函数先用独立测试 URL 验证完整链路。 2. 新 OSS Bucket 先用测试客户端验证上传和拉取。 3. 如果使用自有域名,切 CNAME/路由到新函数。 4. 如果必须改 Unity 硬编码 URL,先发测试包,用 `Tools/Steam 上传流程测试器` 验证。 5. 保留旧函数和旧 Bucket 一段时间,直到确认没有老客户端继续写入。 ### 12.6 回滚 回滚条件: - 大量 `STS request failed`。 - `Steam verification failed` 异常升高。 - OSS PostObject 403/Policy 失败。 - 查看器无法拉取新对象。 回滚方式: - 域名切回旧函数。 - 或客户端测试包退回旧 `FunctionUrl`。 - OSS 写入如果已经分散到新旧 Bucket,需要按时间窗口补复制新 Bucket 新增对象回旧 Bucket。 --- ## 13. 上线验证清单 ### 13.1 本地代码检查 在函数目录运行: ```powershell cd Tools/OSS/game-upload-function npm run check ``` 该命令会执行: - `node -c index.js` - `node check-upload-contract.js` ### 13.2 云端接口验证 需要 Steam 登录态和真实 Auth Ticket 的测试,建议用 Unity 的 `Tools/Steam 上传流程测试器`: - `action=steamauth` 预校验成功。 - `type=ossdata` 上传成功。 - `type=collectdata` 上传成功。 - `type=bugreport` 返回 `bugreport/...zip` 且每次 key 不同。 - `type=multilingualreport` 返回 `multilingualreport/...zip` 且每次 key 不同。 - `type=questionnaire` 返回 `questionnaire/...json` 且每次 key 不同。 - 错误 objectKey 上传被 OSS 拒绝。 - 超大文件被客户端或 OSS Policy 拒绝。 ### 13.3 OSS 验证 - OSS 控制台能看到新对象。 - `collect/` 可以被 Unity OSS 编辑器拉取。 - `bugreport/` 可以被 PlayerBugViewer 拉取和解析。 - `multilingualreport/` 可以被对应查看器拉取和解析。 - `questionnaire/` 可以被对应查看器拉取和解析。 ### 13.4 日志验证 Function Compute 日志里应能看到: - `[服务启动]` - `[请求开始]` - `[Steam验证]` - `[STS签发]` - `[PostPolicy]` - `[请求完成]` 异常日志要重点看: - 缺少环境变量。 - Steam API 请求超时或 `Ticket for other app`。 - Tablestore 读写失败。 - STS `AssumeRole` 失败。 - OSS PostObject 403。 --- ## 14. 常见问题定位 | 现象 | 优先检查 | |------|----------| | STS 请求 500 | 函数环境变量、RAM 权限、`ROLE_ARN`、Tablestore Endpoint/实例名。 | | STS 请求 403,Steam 验证失败 | `STEAM_API_KEY`、`steamAppId`、允许 AppID、客户端生成的 Auth Ticket 是否属于该 App。 | | `Ticket for other app` | 当前客户端 AppID 与服务端允许列表不一致,或测试包/正式包 Steam AppID 混用。 | | OSS PostObject 403 | `objectKey` 是否被客户端改动,Post Policy 是否过期,Bucket/Endpoint 是否匹配,STS Role 是否有 PutObject。 | | 上传大文件失败 | 对应类型大小限制:普通/collect 3MB,bugreport 10MB,multilingualreport 1MB,questionnaire 512KB。 | | 新环境首次上传变慢 | Tablestore 缓存为空是正常现象;第一次会重新调用 Steam 和 STS。 | | 老版本客户端上传失败 | 检查旧 `FunctionUrl` 是否仍可用,或是否已发布包含新 URL 的客户端。 | --- ## 15. 交接结论 服务端同事需要优先理解以下事实: 1. 当前后端是“上传授权服务”,不是游戏权威服务器。 2. Function Compute 只签发凭证,不保存玩家文件。 3. OSS 是永久文件数据源,迁移时要整桶/全前缀保留 objectKey。 4. Tablestore 是短时缓存,表结构必须有,但缓存行通常不用迁移。 5. RAM / STS 是安全核心,不能放宽到全桶长期写权限。 6. 函数 URL 已硬编码到客户端,迁移切流必须考虑老版本兼容。 7. 任何迁移验证都必须覆盖 Steam 预校验、STS 签发、OSS 直传、开发者查看器拉取四段链路。