From dbf353cba5601be78abd96b53f362582fa9ef627 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 26 Jul 2025 22:19:42 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat(api):=20=E6=B7=BB=E5=8A=A0API=E7=AB=AF?= =?UTF-8?q?=E7=82=B9/beatmaps/{beatmap}/scores/users/{user}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增路由 /beatmaps/{beatmap}/scores/users/{user} 用于查询用户在特定地图上的得分 - 实现了对用户得分的验证和处理,如果用户没有得分则返回 404 错误 - 修正了之前代码中的一些格式问题,如过长的行进行适当的换行 --- app/router/beatmap.py | 66 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/app/router/beatmap.py b/app/router/beatmap.py index f6b8f1a..e9aab61 100644 --- a/app/router/beatmap.py +++ b/app/router/beatmap.py @@ -32,7 +32,9 @@ async def get_beatmap( beatmap = ( await db.exec( select(Beatmap) - .options(joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps)) # pyright: ignore[reportArgumentType] + .options( + joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] + ) .where(Beatmap.id == bid) ) ).first() @@ -70,7 +72,9 @@ async def batch_get_beatmaps( await db.exec( select(Beatmap) .options( - joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] + joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) .order_by(col(Beatmap.last_updated).desc()) .limit(50) @@ -81,7 +85,9 @@ async def batch_get_beatmaps( await db.exec( select(Beatmap) .options( - joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] + joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) .where(col(Beatmap.id).in_(b_ids)) .limit(50) @@ -97,7 +103,7 @@ class BeatmapScores(BaseModel): @router.get( - "/beatmaps/{beatmap}/scores", tags=["beatmapset"], response_model=BeatmapScores + "/beatmaps/{beatmap}/scores", tags=["beatmap"], response_model=BeatmapScores ) async def get_beatmapset_scores( beatmap: int, @@ -126,7 +132,9 @@ async def get_beatmapset_scores( .options( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] - .selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] + .selectinload( + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) .where(Score.beatmap_id == beatmap) .where(Score.user_id == current_user.id) @@ -137,3 +145,51 @@ async def get_beatmapset_scores( scores=[ScoreResp.from_db(score) for score in all_scores], userScore=ScoreResp.from_db(user_score) if user_score else None, ) + + +class BeatmapUserScore(BaseModel): + position: int + score: ScoreResp + + +@router.get( + "/beatmaps/{beatmap}/scores/users/{user}", + tags=["beatmap"], + response_model=BeatmapUserScore, +) +async def get_user_beatmap_score( + beatmap: int, + user: int, + legacy_only: bool = Query(None), + mode: str = Query(None), + mods: str = Query(None), # TODO:添加mods筛选 + current_user: DBUser = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + if legacy_only: + raise HTTPException( + status_code=404, detail="This server only contains non-legacy scores" + ) + user_score = ( + await db.exec( + select(Score) + .options( + joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] + .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] + .selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] + ) + .where(Score.beatmap_id == beatmap) + .where(Score.user_id == user) + .order_by(col(Score.classic_total_score).desc()) + ) + ).first() + + if not user_score: + raise HTTPException( + status_code=404, detail="Cannot find user %s's score on this beatmap" % user + ) + else: + return BeatmapUserScore( + position=user_score.position if user_score.position is not None else 0, + score=ScoreResp.from_db(user_score), + ) From 33a41916384c99656635183bae128578cec87ebc Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 26 Jul 2025 22:35:31 +0800 Subject: [PATCH 2/6] =?UTF-8?q?chore(test):=20=20=E4=B8=BA/api/v2/beatmaps?= =?UTF-8?q?/{beatmap=5Fid}/scores/users/{user=5Fid}=E7=AB=AF=E7=82=B9?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test_api.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/test_api.py b/test_api.py index d11ab7b..861b28f 100644 --- a/test_api.py +++ b/test_api.py @@ -124,6 +124,24 @@ def get_beatmap_scores(access_token: str, beatmap_id: int): return None +def get_user_beatmap_score(access_token: str, beatmap_id: int, user_id: int): + """获取玩家成绩""" + url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores/users/{user_id}" + headers = {"Authorization": f"Bearer {access_token}"} + try: + response = requests.get(url, headers=headers) + if response.status_code == 200: + print(f"✅ 成功获取谱面 {beatmap_id} 中用户 {user_id} 的成绩数据") + return response.json() + else: + print(f"❌ 获取谱面成绩失败: {response.status_code}") + print(f"响应内容: {response.text}") + return None + except Exception as e: + print(f"❌ 获取谱面成绩请求失败: {e}") + return None + + def main(): """主测试函数""" print("=== osu! API 模拟服务器测试 ===\n") @@ -180,8 +198,17 @@ def main(): else: print("用户在该谱面没有成绩记录") - # 6. 测试令牌刷新 - print("\n6. 测试令牌刷新...") + # 6. 测试谱面指定用户成绩 + user_score = get_user_beatmap_score(token_data["access_token"], 1, 1) + if user_score: + print(f"用户成绩ID:{user_score['score']['id']}") + print(f"此成绩acc:{user_score['score']['accuracy']}") + print(f"总分:{user_score['score']['classic_total_score']}") + else: + print("该用户在此谱面没有记录") + + # 7. 测试令牌刷新 + print("\n7. 测试令牌刷新...") new_token_data = refresh_token(token_data["refresh_token"]) if new_token_data: print(f"新访问令牌: {new_token_data['access_token']}") From e5a6ca70c6eba2f604888fa6e48ecd0888aaaafb Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 26 Jul 2025 22:41:05 +0800 Subject: [PATCH 3/6] =?UTF-8?q?refactor(api):=20=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E7=AB=AF=E7=82=B9/beatmaps/{beatmap}/scores=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E7=9A=84=E5=87=BD=E6=95=B0=E5=90=8D=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/router/beatmap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/router/beatmap.py b/app/router/beatmap.py index e9aab61..30e2ef1 100644 --- a/app/router/beatmap.py +++ b/app/router/beatmap.py @@ -105,7 +105,7 @@ class BeatmapScores(BaseModel): @router.get( "/beatmaps/{beatmap}/scores", tags=["beatmap"], response_model=BeatmapScores ) -async def get_beatmapset_scores( +async def get_beatmap_scores( beatmap: int, legacy_only: bool = Query(None), # TODO:加入对这个参数的查询 mode: str = Query(None), @@ -192,4 +192,4 @@ async def get_user_beatmap_score( return BeatmapUserScore( position=user_score.position if user_score.position is not None else 0, score=ScoreResp.from_db(user_score), - ) + ) \ No newline at end of file From baa16e1be6bb134f295e402790a6c03e8bc98b36 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 26 Jul 2025 22:50:28 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat(api):=20=E6=B7=BB=E5=8A=A0/beatmaps/{b?= =?UTF-8?q?eatmap}/scores/users/{user}/all=20=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/router/beatmap.py | 66 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/app/router/beatmap.py b/app/router/beatmap.py index 30e2ef1..697cf0a 100644 --- a/app/router/beatmap.py +++ b/app/router/beatmap.py @@ -33,8 +33,10 @@ async def get_beatmap( await db.exec( select(Beatmap) .options( - joinedload(Beatmap.beatmapset).selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] - ) + joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) + ) .where(Beatmap.id == bid) ) ).first() @@ -72,9 +74,11 @@ async def batch_get_beatmaps( await db.exec( select(Beatmap) .options( - joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] - ) + joinedload( + Beatmap.beatmapset # pyright: ignore[reportArgumentType] + ).selectinload( + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) .order_by(col(Beatmap.last_updated).desc()) .limit(50) @@ -85,9 +89,11 @@ async def batch_get_beatmaps( await db.exec( select(Beatmap) .options( - joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] - ) + joinedload( + Beatmap.beatmapset # pyright: ignore[reportArgumentType] + ).selectinload( + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) .where(col(Beatmap.id).in_(b_ids)) .limit(50) @@ -133,7 +139,7 @@ async def get_beatmap_scores( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] .selectinload( - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) ) .where(Score.beatmap_id == beatmap) @@ -176,10 +182,13 @@ async def get_user_beatmap_score( .options( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] - .selectinload(Beatmapset.beatmaps) # pyright: ignore[reportArgumentType] + .selectinload( + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) .where(Score.beatmap_id == beatmap) .where(Score.user_id == user) + .where(Score.gamemode == mode) .order_by(col(Score.classic_total_score).desc()) ) ).first() @@ -192,4 +201,39 @@ async def get_user_beatmap_score( return BeatmapUserScore( position=user_score.position if user_score.position is not None else 0, score=ScoreResp.from_db(user_score), - ) \ No newline at end of file + ) + + +@router.get( + "/beatmaps/{beatmap}/scores/users/{user}/all", + tags=["beatmap"], + response_model=list[ScoreResp], +) +async def get_user_all_beatmap_scores( + beatmap: int, + user: int, + legacy_only: bool = Query(None), + ruleset: str = Query(None), + current_user: DBUser = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + if legacy_only: + raise HTTPException(status_code=404,detail="This server only contains non-legacy scores") + all_user_scores=( + await db.exec( + select(Score) + .options( + joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] + .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] + .selectinload( + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) + ) + .where(Score.gamemode==ruleset) + .where(Score.beatmap_id == beatmap) + .where(Score.user_id == user) + .order_by(col(Score.classic_total_score).desc()) + ) + ).all() + + return [ScoreResp.from_db(score) for score in all_user_scores] From b69cff6bd4e7c032be5bb529d869bcea7d9e64bb Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 26 Jul 2025 22:58:14 +0800 Subject: [PATCH 5/6] =?UTF-8?q?fix(api):=20=E4=BF=AE=E5=A4=8D/beatmaps/{be?= =?UTF-8?q?atmap}/scores/users/{user}=E7=AB=AF=E7=82=B9=E4=B8=AD=E4=B8=8D?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E7=9A=84=E8=AE=A1=E7=AE=97=E6=8E=92=E5=90=8D?= =?UTF-8?q?=E7=9A=84=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/router/beatmap.py | 46 +++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/app/router/beatmap.py b/app/router/beatmap.py index 697cf0a..27555ad 100644 --- a/app/router/beatmap.py +++ b/app/router/beatmap.py @@ -33,9 +33,11 @@ async def get_beatmap( await db.exec( select(Beatmap) .options( - joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] + joinedload( + Beatmap.beatmapset # pyright: ignore[reportArgumentType] + ).selectinload( Beatmapset.beatmaps # pyright: ignore[reportArgumentType] - ) + ) ) .where(Beatmap.id == bid) ) @@ -75,7 +77,7 @@ async def batch_get_beatmaps( select(Beatmap) .options( joinedload( - Beatmap.beatmapset # pyright: ignore[reportArgumentType] + Beatmap.beatmapset # pyright: ignore[reportArgumentType] ).selectinload( Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) @@ -90,8 +92,8 @@ async def batch_get_beatmaps( select(Beatmap) .options( joinedload( - Beatmap.beatmapset # pyright: ignore[reportArgumentType] - ).selectinload( + Beatmap.beatmapset # pyright: ignore[reportArgumentType] + ).selectinload( Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) ) @@ -144,6 +146,7 @@ async def get_beatmap_scores( ) .where(Score.beatmap_id == beatmap) .where(Score.user_id == current_user.id) + .order_by(col(Score.classic_total_score).desc()) ) ).first() @@ -176,22 +179,29 @@ async def get_user_beatmap_score( raise HTTPException( status_code=404, detail="This server only contains non-legacy scores" ) - user_score = ( + all_scores = ( await db.exec( select(Score) .options( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] .selectinload( - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] - ) + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) - .where(Score.beatmap_id == beatmap) - .where(Score.user_id == user) .where(Score.gamemode == mode) + .where(Score.beatmap_id == beatmap) .order_by(col(Score.classic_total_score).desc()) ) - ).first() + ).all() + + rank = 1 + user_score = None + for score in all_scores: + if score.user_id == user: + user_score = score + break + rank += 1 if not user_score: raise HTTPException( @@ -199,7 +209,7 @@ async def get_user_beatmap_score( ) else: return BeatmapUserScore( - position=user_score.position if user_score.position is not None else 0, + position=rank, score=ScoreResp.from_db(user_score), ) @@ -218,22 +228,24 @@ async def get_user_all_beatmap_scores( db: AsyncSession = Depends(get_db), ): if legacy_only: - raise HTTPException(status_code=404,detail="This server only contains non-legacy scores") - all_user_scores=( + raise HTTPException( + status_code=404, detail="This server only contains non-legacy scores" + ) + all_user_scores = ( await db.exec( select(Score) .options( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] .selectinload( - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) ) - .where(Score.gamemode==ruleset) + .where(Score.gamemode == ruleset) .where(Score.beatmap_id == beatmap) .where(Score.user_id == user) .order_by(col(Score.classic_total_score).desc()) ) ).all() - + return [ScoreResp.from_db(score) for score in all_user_scores] From d23de2c166960639beff4074d65292dd1d7805e5 Mon Sep 17 00:00:00 2001 From: jimmy-sketch Date: Sat, 26 Jul 2025 23:03:45 +0800 Subject: [PATCH 6/6] =?UTF-8?q?Revert=20"fix(api):=20=E4=BF=AE=E5=A4=8D/be?= =?UTF-8?q?atmaps/{beatmap}/scores/users/{user}=E7=AB=AF=E7=82=B9=E4=B8=AD?= =?UTF-8?q?=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E8=AE=A1=E7=AE=97=E6=8E=92?= =?UTF-8?q?=E5=90=8D=E7=9A=84=E7=AE=97=E6=B3=95"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit b69cff6bd4e7c032be5bb529d869bcea7d9e64bb. --- app/router/beatmap.py | 46 ++++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/app/router/beatmap.py b/app/router/beatmap.py index 27555ad..697cf0a 100644 --- a/app/router/beatmap.py +++ b/app/router/beatmap.py @@ -33,11 +33,9 @@ async def get_beatmap( await db.exec( select(Beatmap) .options( - joinedload( - Beatmap.beatmapset # pyright: ignore[reportArgumentType] - ).selectinload( + joinedload(Beatmap.beatmapset).selectinload( # pyright: ignore[reportArgumentType] Beatmapset.beatmaps # pyright: ignore[reportArgumentType] - ) + ) ) .where(Beatmap.id == bid) ) @@ -77,7 +75,7 @@ async def batch_get_beatmaps( select(Beatmap) .options( joinedload( - Beatmap.beatmapset # pyright: ignore[reportArgumentType] + Beatmap.beatmapset # pyright: ignore[reportArgumentType] ).selectinload( Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) @@ -92,8 +90,8 @@ async def batch_get_beatmaps( select(Beatmap) .options( joinedload( - Beatmap.beatmapset # pyright: ignore[reportArgumentType] - ).selectinload( + Beatmap.beatmapset # pyright: ignore[reportArgumentType] + ).selectinload( Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) ) @@ -146,7 +144,6 @@ async def get_beatmap_scores( ) .where(Score.beatmap_id == beatmap) .where(Score.user_id == current_user.id) - .order_by(col(Score.classic_total_score).desc()) ) ).first() @@ -179,29 +176,22 @@ async def get_user_beatmap_score( raise HTTPException( status_code=404, detail="This server only contains non-legacy scores" ) - all_scores = ( + user_score = ( await db.exec( select(Score) .options( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] .selectinload( - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] - ) + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + ) ) - .where(Score.gamemode == mode) .where(Score.beatmap_id == beatmap) + .where(Score.user_id == user) + .where(Score.gamemode == mode) .order_by(col(Score.classic_total_score).desc()) ) - ).all() - - rank = 1 - user_score = None - for score in all_scores: - if score.user_id == user: - user_score = score - break - rank += 1 + ).first() if not user_score: raise HTTPException( @@ -209,7 +199,7 @@ async def get_user_beatmap_score( ) else: return BeatmapUserScore( - position=rank, + position=user_score.position if user_score.position is not None else 0, score=ScoreResp.from_db(user_score), ) @@ -228,24 +218,22 @@ async def get_user_all_beatmap_scores( db: AsyncSession = Depends(get_db), ): if legacy_only: - raise HTTPException( - status_code=404, detail="This server only contains non-legacy scores" - ) - all_user_scores = ( + raise HTTPException(status_code=404,detail="This server only contains non-legacy scores") + all_user_scores=( await db.exec( select(Score) .options( joinedload(Score.beatmap) # pyright: ignore[reportArgumentType] .joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType] .selectinload( - Beatmapset.beatmaps # pyright: ignore[reportArgumentType] + Beatmapset.beatmaps # pyright: ignore[reportArgumentType] ) ) - .where(Score.gamemode == ruleset) + .where(Score.gamemode==ruleset) .where(Score.beatmap_id == beatmap) .where(Score.user_id == user) .order_by(col(Score.classic_total_score).desc()) ) ).all() - + return [ScoreResp.from_db(score) for score in all_user_scores]