# 09 存档与后端

本文档定义新项目的存档模型与后端接口。目标：稳定、可迁移、可扩展、可观测。即使是单机为主，也建议提供云端存档能力以支持跨设备与版本兼容。

## 9.1 存档设计目标

- 数据完整：可恢复到任意一个确定状态
- 可迁移：版本升级可做增量迁移
- 可校验：避免异常/作弊数据导致崩档
- 可压缩：存档体积可控，读写快速

## 9.2 存档分层与结构

存档建议由三部分组成：

- `meta`：版本、时间戳、校验、内容包版本
- `player`：玩家状态与资产
- `world`：世界状态（时间、地点资源、灾害、标记）

```json
{
  "meta": {
    "saveVersion": 3,
    "contentVersion": {"base": "1.0.0", "packs": {"event_pack_01": "1.0.0"}},
    "createdAt": 1730000000000,
    "updatedAt": 1730001234000,
    "checksum": "sha256:..."
  },
  "player": {
    "name": "Player",
    "placeId": "home",
    "stats": {"life": 80, "hunger": 40, "thirst": 60, "energy": 30, "san": 50, "radiation": 10},
    "attributes": {"str": 4, "agi": 5, "int": 6, "per": 5, "luck": 3},
    "inventory": [{"itemId": "water_purified", "count": 2, "durability": null}],
    "equipment": {"weapon": "knife_01", "body": "jacket_01"},
    "buffs": [{"buffId": "infection_1", "startedAt": 1730000500000, "stacks": 1}],
    "quests": {"active": ["q_main_01"], "completed": ["q_tutorial_01"]}
  },
  "world": {
    "time": {"baseTime": 0, "minutesFromStart": 1234},
    "flags": {"met_trader": true},
    "places": {
      "ruins_store": {
        "heat": 3,
        "resourceStock": {"water_dirty": 0, "scrap_metal": 2},
        "lastRefreshAt": 1730001200000
      }
    },
    "disaster": {"activeId": "sandstorm", "endsAt": 1730003000000}
  }
}
```

## 9.3 存档写入策略

- 自动存档触发点（建议）：
  - 回到基地
  - 完成任务步骤
  - 战斗结束
  - 灾害开始/结束
  - 重要事件选择后
- 快速保存：
  - UI 提供手动保存按钮
  - 保存期间不阻塞主线程（使用异步/worker）
- 防止坏档：
  - 写入采用“双缓冲”：先写临时，再替换正式
  - 写入失败保留上一份

## 9.4 存档校验与迁移

### 9.4.1 校验（客户端与服务端）

- Schema 校验：字段类型、必填字段、范围
- 引用校验：itemId/placeId/questId 必须存在于内容库
- 数值校验：状态上下限、背包容量、堆叠上限

### 9.4.2 迁移（SaveVersion）

- 每次内容/结构变化提升 `saveVersion`
- 迁移必须是纯函数：`migrate(save_vN) -> save_vN+1`
- 迁移记录：
  - 变更点
  - 默认值策略
  - 兼容策略（删字段/改名/拆分）

## 9.5 后端能力范围（建议）

后端建议至少提供：

- 账号与鉴权（匿名账号也可）
- 云端存档 CRUD
- 内容包版本与更新清单（可选）
- 运行日志/错误上报（可选）

## 9.6 API 约定（REST 示例）

### 9.6.1 鉴权

- 登录（匿名/设备绑定）：
  - `POST /api/v1/auth/anonymous`
  - 返回 `accessToken/refreshToken`
- 请求头：
  - `Authorization: Bearer <accessToken>`

### 9.6.2 存档接口

- 列表：
  - `GET /api/v1/saves`
- 读取：
  - `GET /api/v1/saves/{slotId}`
- 写入（覆盖）：
  - `PUT /api/v1/saves/{slotId}`
  - Body：完整存档 JSON
  - 服务端校验通过后写入
- 删除：
  - `DELETE /api/v1/saves/{slotId}`

### 9.6.3 并发与冲突

使用乐观锁：

- 存档 meta 带 `etag` 或 `rev`
- 更新时带 `If-Match: <etag>`
- 冲突返回 409，客户端提示“本地与云端不一致”

## 9.7 速率限制与滥用防护

- 写入频率限制：例如每分钟最多 10 次
- 存档大小限制：例如 1MB
- 结构化错误码，避免泄漏服务端信息

## 9.8 旧版 PHP 存档参考（当前仓库）

当前仓库包含简单的 PHP+MySQL 存档端点：

- [save.php](file:///Users/virtheart/Documents/CloneProjects/TinyWaste/save.php)：按 `key/data/overwrite` 写入 `record(key,data)`，并校验 `data` 是 JSON
- [load.php](file:///Users/virtheart/Documents/CloneProjects/TinyWaste/load.php)：按 `key` 读取

新项目不建议继续沿用 SAE 环境变量模式与弱协议，建议升级为标准鉴权、版本控制与迁移机制。

```mermaid
sequenceDiagram
  participant C as Client
  participant API as Save API
  participant DB as Database
  C->>API: PUT /saves/{slotId} (save, If-Match)
  API->>API: 校验Schema/引用/数值
  API->>DB: 写入(事务)
  DB-->>API: ok
  API-->>C: 200 (new etag)
```

对应源文件：[save-flow.mmd](./diagrams/save-flow.mmd)

