# 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 直传、开发者查看器拉取四段链路。