GraphQL如何实现新分数更高时才执行排行榜条目条件upsert?
结论
这个逻辑完全可以实现,你当前写的原生upsert满足不了需求——现有代码只要匹配到对应id的条目,就会无条件覆盖score字段,根本不会判断新分数和历史分数的高低。
推荐实现方案(无竞态问题)
不要用单次upsert逻辑,GraphCMS本身支持数值比较类的过滤运算符,你可以把更新和创建逻辑放在同一个mutation里提交,所有判断都在服务端执行,从根源避免并发覆盖问题:
mutation submitRankEntry($id: String!, $newScore: Int!) { # 仅当条目存在、且历史分数低于新分数时,才执行更新 updateManyRankentries( where: { id: $id, score_lt: $newScore } data: { score: $newScore } ) { count } # 仅当条目不存在时,创建新条目 createRankentry(data: { id: $id, score: $newScore }) { id } }
对应的前端fetch逻辑里,只需要忽略「id唯一约束冲突」的报错即可——触发这个报错只有一种场景:对应id的条目已经存在,且历史分数大于等于你提交的新分数,这时候更新操作匹配不到条目(count返回0),创建操作因为主键重复失败,刚好符合「新分数不更高就不改动数据」的要求。
参考JS调用代码:
const submitScore = async (entryId, score) => { const res = await fetch('你的GraphCMS接口地址', { method: 'POST', headers: { 'Authorization': 'Bearer 你的API鉴权token', 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` mutation submitRankEntry($id: String!, $newScore: Int!) { updateManyRankentries( where: { id: $id, score_lt: $newScore } data: { score: $newScore } ) { count } createRankentry(data: { id: $id, score: $newScore }) { id } } `, variables: { id: entryId, newScore: score } }) }) const result = await res.json() // 过滤掉主键重复的预期内错误,其余错误正常抛出 if (result.errors?.length && !result.errors.every(err => err.message.includes('Unique constraint'))) { throw new Error('分数提交失败') } return result }
不推荐方案:前端先查后改
你也可以先发起一次query查询对应id的历史分数,判断新分数更高之后再调用upsert提交:
query getEntryScore($id: String!) { rankentry(where: {id: $id}) { score } }
但这个方案存在竞态漏洞:如果查询完成后、提交更新前,有其他用户提交了更高的分数,你的请求依然会把更高的分数覆盖掉,只适合并发量极低的测试场景,正式环境不要用。
内容的提问来源于stack exchange,提问作者Imrane
相关产品推荐
相关产品推荐

