feat: 新增技能级局部更新的 PATCH 接口

新增 PATCH /api/heroes/:uuid/skills/:skillUuid 接口,支持按层级合并技能字段及 lv_entries,避免整表替换技能数据。同时更新 API 文档,说明该接口的功能与使用方式。
This commit is contained in:
panFD
2026-10-05 17:18:01 +08:00
parent 0db3404481
commit 04cea0fbb2
2 changed files with 83 additions and 1 deletions

View File

@@ -30,7 +30,8 @@ npm run dev:remote # 线上设计版(本地模拟 remote)
| `GET` | `/api/heroes` | 获取全部英雄列表 | | `GET` | `/api/heroes` | 获取全部英雄列表 |
| `GET` | `/api/heroes/:uuid` | 获取单个英雄完整配置 | | `GET` | `/api/heroes/:uuid` | 获取单个英雄完整配置 |
| `POST` | `/api/heroes/:uuid` | **新增或覆盖**单个英雄(body 为完整英雄对象) | | `POST` | `/api/heroes/:uuid` | **新增或覆盖**单个英雄(body 为完整英雄对象) |
| `PUT` | `/api/heroes/:uuid` | **局部更新**单个英雄(body 为需修改的字段) | | `PUT` | `/api/heroes/:uuid` | **局部更新**单个英雄(body 为需修改的字段,顶层浅合并) |
| `PATCH` | `/api/heroes/:uuid/skills/:skillUuid` | **技能级局部更新**:支持按 `lv` 合并 `lv_entries`,避免整表替换 `skills` |
| `DELETE` | `/api/heroes/:uuid` | 删除单个英雄 | | `DELETE` | `/api/heroes/:uuid` | 删除单个英雄 |
| `POST` | `/api/refresh` | 刷新底座缓存(修改 SkillSet.ts 等后调用) | | `POST` | `/api/refresh` | 刷新底座缓存(修改 SkillSet.ts 等后调用) |

View File

@@ -214,6 +214,87 @@ app.delete('/api/heroes/:uuid', async (req, res) => {
} }
}) })
/**
* 技能级局部更新:PATCH /api/heroes/:uuid/skills/:skillUuid
* 支持对技能字段及 lv_entries 按 lv 合并,避免整表替换 skills 数组。
*
* body 格式:
* {
* name?, cd?, cdExpr?, desc?, // 技能基础字段,存在则覆盖
* overrides?, // 与现有 overrides 浅合并(不是替换)
* lv_entries?: [ // 按 lv 合并:存在则更新该档,不存在则新增
* { lv, s_lv?, overrides?, desc? }
* ]
* }
*/
app.patch('/api/heroes/:uuid/skills/:skillUuid', async (req, res) => {
try {
if (!(await guardVersion(res))) return
const uuid = Number(req.params.uuid)
const skillUuid = Number(req.params.skillUuid)
if (Number.isNaN(uuid) || Number.isNaN(skillUuid)) {
return res.status(400).json({ error: 'uuid 与 skillUuid 必须为数字' })
}
const patch = req.body
if (!patch || typeof patch !== 'object') {
return res.status(400).json({ error: 'body 必须为技能补丁对象' })
}
const heroes = await store.getHeroes()
const hIdx = heroes.findIndex((h) => h.uuid === uuid)
if (hIdx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` })
const hero = heroes[hIdx]
const sIdx = hero.skills.findIndex((s) => s.uuid === skillUuid)
if (sIdx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在技能 ${skillUuid}` })
const skill = hero.skills[sIdx]
// 基础字段直接覆盖
if (patch.name !== undefined) skill.name = patch.name
if (patch.cd !== undefined) skill.cd = patch.cd
if (patch.cdExpr !== undefined) skill.cdExpr = patch.cdExpr
if (patch.desc !== undefined) skill.desc = patch.desc
// overrides 浅合并(不是替换)
if (patch.overrides && typeof patch.overrides === 'object') {
skill.overrides = { ...skill.overrides, ...patch.overrides }
}
// lv_entries 按 lv 合并:同 lv 更新,不同 lv 新增
if (Array.isArray(patch.lv_entries)) {
if (!Array.isArray(skill.lv_entries)) skill.lv_entries = []
for (const entry of patch.lv_entries) {
if (!entry || typeof entry !== 'object') continue
const lv = entry.lv
if (lv === undefined) continue
const eIdx = skill.lv_entries.findIndex((e) => e.lv === lv)
if (eIdx >= 0) {
// 同 lv:浅合并 overrides,覆盖其他字段
const existing = skill.lv_entries[eIdx]
skill.lv_entries[eIdx] = {
...existing,
...entry,
overrides: entry.overrides
? { ...existing.overrides, ...entry.overrides }
: existing.overrides,
}
} else {
// 新 lv:直接插入
skill.lv_entries.push(entry)
}
}
// 按 lv 升序排序,保持数据整洁
skill.lv_entries.sort((a, b) => a.lv - b.lv)
}
await store.saveHeroes(heroes)
res.json({ ok: true, uuid, skillUuid, action: 'skill_patched' })
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 预设库:GET 从线上库读取 */ /** 预设库:GET 从线上库读取 */
app.get('/api/templates', async (_req, res) => { app.get('/api/templates', async (_req, res) => {
try { try {