本文档定义后端服务的功能需求和行为规范。
塔防游戏后端服务,负责游戏配置下发、结果验证和排行榜管理。
- 防作弊:验证客户端提交的游戏数据可信性
- 配置权威:所有游戏配置由服务端定义
- 状态管理:维护游戏会话和波次记录
- 服务端权威:怪物属性、建筑属性、波次配置由服务端定义
- 客户端零配置:客户端不内置任何游戏数值
- 渐进验证:每波验证一次,而非游戏结束时一次性验证
- 服务端生成:波次配置由服务端随机生成并下发给客户端
会话管理:
- 创建游戏会话,返回配置和第一波
- 接收波次提交,验证并返回下一波
- 结束游戏会话,记录排行榜
- 定时清理过期会话(24 小时)
排行榜:
- 记录玩家成绩(昵称、分数、波次)
- 查询排行榜列表
- 验证失败日志记录
- 异常行为告警
- 成绩回放验证
- 玩家历史记录
POST /api/game/sessions- 创建会话POST /api/game/sessions/wave- 提交波次POST /api/game/sessions/end- 结束游戏GET /api/game/leaderboard- 查询排行榜
创建游戏会话。
请求:无参数
响应:
{
"sessionId": "uuid",
"config": {
"buildings": { ... },
"monsters": { ... },
"map": { ... },
"initial": { "money": 500, "life": 100, "difficulty": 1.0 }
},
"firstWave": {
"waveNumber": 1,
"monsters": [
{ "id": "uuid", "type": 0, "life": 50, "speed": 3.5, "shield": 0, "money": 5 }
]
}
}提交波次结果。
请求:
{
"sessionId": "uuid",
"waveNumber": 1,
"actions": [
{ "type": "BUILD", "frame": 100, "buildingType": "cannon", "buildingId": "b-001", "position": [5, 5] }
],
"attacks": [
{
"frame": 200,
"buildingId": "b-001",
"originalTargetId": "m-001",
"originalTargetPosition": [6, 5],
"monsterId": "m-001",
"monsterPosition": [6, 5],
"damage": 10
}
],
"result": {
"killed": 3,
"killedByType": { "0": 3 },
"passed": 0,
"scoreGained": 10,
"moneyGained": 15,
"lifeLost": 0,
"totalDamageDealt": 150,
"totalLifeDestroyed": 150,
"waveDurationFrames": 1000
},
"buildings": [
{ "id": "b-001", "type": "cannon", "position": [5, 5], "level": 1, "damageDealt": 150, "kills": 3 }
]
}响应:
{
"valid": true,
"serverState": {
"money": 515,
"score": 10,
"life": 100,
"difficulty": 1.2
},
"nextWave": {
"waveNumber": 2,
"monsters": [ ... ],
"lifeReward": 0
}
}结束游戏会话。
请求:
{
"sessionId": "uuid",
"nickname": "Player1",
"lastWave": { ... } // 可选,提前结束时不提供
}响应:
{
"verified": true,
"ranking": {
"rank": 5,
"total": 100,
"isNewRecord": false
}
}查询排行榜。
请求参数:
limit(可选):返回条数,默认 10,最大 100
响应:
{
"entries": [
{ "rank": 1, "nickname": "Player1", "score": 1000, "wavesCompleted": 42, "createdAt": "2024-01-01T00:00:00Z" }
]
}游戏会话,存储游戏进行中的状态。
字段:
id(UUID): 主键created_at(DateTime): 创建时间money(Integer): 当前金钱score(Integer): 当前分数life(Integer): 当前生命wave_count(Integer): 已完成波次数difficulty(Float): 当前难度系数buildings(JSON): 建筑列表config(JSON): 游戏配置next_wave(JSON): 下一波配置
生命周期:
- 创建:POST /sessions
- 更新:POST /sessions/wave
- 删除:POST /sessions/end 或定时清理
波次记录,不可变的历史数据。
字段:
id(UUID): 主键session(FK): 关联会话wave_number(Integer): 波次号created_at(DateTime): 创建时间killed(Integer): 击杀数passed(Integer): 穿过数remaining(Integer): 剩余数(提前结束时)score_gained(Integer): 获得分数money_gained(Integer): 获得金钱life_lost(Integer): 损失生命total_damage_dealt(Integer): 总伤害wave_duration_frames(Integer): 持续帧数money_spent(Integer): 花费金钱(服务端计算)money_income(Integer): 回收金钱(服务端计算)building_count(Integer): 建筑数量end_money/score/life/difficulty: 状态快照
约束:
unique_together: [session, wave_number]- 只创建不更新
排行榜条目。
字段:
id(UUID): 主键nickname(String, max=32): 玩家昵称score(Integer): 最终得分waves_completed(Integer): 完成波次created_at(DateTime): 记录时间
排序:
- 主排序:score 降序
- 次排序:waves_completed 降序
采用多层验证架构,详见 安全设计文档。
波次验证通过后,服务端计算新状态:
new_money = old_money - spent + income + moneyGained
new_score = old_score + scoreGained
new_life = old_life - lifeLost
new_difficulty = calc_new_difficulty(old_difficulty, lifeLost, wave)
昵称:
- 长度:1-32 字符
- 不能为纯空白
分数:
- 必须大于 0 才能上榜
波次:
- 必须连续提交,不能跳波
难度调整:
- Wave 1 不调整难度
- 0 伤害:难度增加 5%-20%
- 受伤:难度降低 10%-40%
- 难度最小值:1.0
生命奖励:
- 每 5 波:+5 生命
- 每 10 波:+10 生命
- 不超过 100
会话有效期:
- 最长 24 小时
- 超时自动清理
- 不支持 Level 3 行为重放验证
- 路径验证只检查方向合法性,不验证具体路径
- 统计分析仅记录日志,不阻断请求
会话管理:
- 创建会话返回正确配置
- 波次提交后状态正确更新
- 游戏结束后会话被删除
- 过期会话被正确清理
数据验证:
- Level 1 验证拦截数量不一致
- Level 2 验证拦截 DPS 超限和伪造攻击
- 验证失败返回正确错误信息
排行榜:
- 成绩正确记录
- 排名正确计算
- 查询返回正确结果
- 正常游戏:完整流程测试
- 验证失败:各层级验证错误测试
- 会话过期:会话不存在(HTTP 404)测试
- 并发提交:防止重复提交测试
- 边界值:0 分、最大波次等
- API 响应:< 200ms(P95)
- 验证耗时:< 100ms
- 数据库查询:< 50ms