2025-12-16 18:06:50 +08:00
|
|
|
|
"""
|
|
|
|
|
|
游戏相关路由
|
|
|
|
|
|
"""
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
from typing import List
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from ..core.database import get_db
|
|
|
|
|
|
from ..schemas.game import (
|
|
|
|
|
|
ChestCreate, ChestResponse, BetCreate, BetResponse,
|
2025-12-18 15:03:25 +08:00
|
|
|
|
ChestSettle, ChestLock,
|
|
|
|
|
|
ChestTemplateCreate, ChestTemplateUpdate, ChestTemplateResponse
|
2025-12-16 18:06:50 +08:00
|
|
|
|
)
|
|
|
|
|
|
from ..services.game_service import GameService
|
|
|
|
|
|
from ..models.game import ChestStatus
|
|
|
|
|
|
from ..utils.deps import get_current_user, get_current_streamer
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api", tags=["game"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/chests", response_model=ChestResponse)
|
|
|
|
|
|
def create_chest(
|
|
|
|
|
|
chest_data: ChestCreate,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
创建宝箱(仅主播)
|
|
|
|
|
|
"""
|
2025-12-18 13:28:29 +08:00
|
|
|
|
print(f"[DEBUG] Creating chest: user={current_user.id}, role={current_user.role}, data={chest_data}")
|
2025-12-16 18:06:50 +08:00
|
|
|
|
try:
|
|
|
|
|
|
chest = GameService.create_chest(db, current_user, chest_data)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算剩余时间
|
|
|
|
|
|
time_remaining = 0
|
|
|
|
|
|
if chest.status == ChestStatus.BETTING:
|
|
|
|
|
|
# 使用本地时间进行计算(与数据库func.now()保持一致)
|
|
|
|
|
|
if isinstance(chest.created_at, str):
|
|
|
|
|
|
if 'T' in chest.created_at:
|
|
|
|
|
|
created_time = datetime.fromisoformat(chest.created_at.replace('Z', ''))
|
|
|
|
|
|
else:
|
|
|
|
|
|
created_time = datetime.strptime(chest.created_at, '%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
else:
|
|
|
|
|
|
created_time = chest.created_at.replace(tzinfo=None) if chest.created_at and chest.created_at.tzinfo else chest.created_at
|
|
|
|
|
|
|
|
|
|
|
|
# 使用本地时间进行计算(func.now()返回的是服务器本地时间)
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
elapsed = (now - created_time).total_seconds()
|
|
|
|
|
|
time_remaining = max(0, int(chest.countdown_seconds - elapsed))
|
|
|
|
|
|
print(f'Created chest {chest.id}: created_at={created_time}, now_local={now}, elapsed={elapsed}, time_remaining={time_remaining}')
|
|
|
|
|
|
|
|
|
|
|
|
# 返回包含time_remaining的响应
|
|
|
|
|
|
return ChestResponse(
|
|
|
|
|
|
id=chest.id,
|
|
|
|
|
|
streamer_id=chest.streamer_id,
|
|
|
|
|
|
title=chest.title,
|
|
|
|
|
|
option_a=chest.option_a,
|
|
|
|
|
|
option_b=chest.option_b,
|
|
|
|
|
|
status=chest.status.value if hasattr(chest.status, 'value') else chest.status,
|
|
|
|
|
|
pool_a=chest.pool_a,
|
|
|
|
|
|
pool_b=chest.pool_b,
|
|
|
|
|
|
total_bets=chest.total_bets,
|
|
|
|
|
|
countdown_seconds=chest.countdown_seconds,
|
|
|
|
|
|
time_remaining=time_remaining,
|
|
|
|
|
|
locked_at=chest.locked_at,
|
|
|
|
|
|
settled_at=chest.settled_at,
|
|
|
|
|
|
result=chest.result,
|
|
|
|
|
|
created_at=chest.created_at
|
|
|
|
|
|
)
|
|
|
|
|
|
except ValueError as e:
|
2025-12-18 13:28:29 +08:00
|
|
|
|
print(f"[DEBUG] Create chest ValueError: {e}")
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"[DEBUG] Create chest Exception: {type(e).__name__}: {e}")
|
2025-12-16 18:06:50 +08:00
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/chests", response_model=List[ChestResponse])
|
|
|
|
|
|
def get_chests(
|
|
|
|
|
|
streamer_id: int = None,
|
|
|
|
|
|
include_history: bool = False,
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取宝箱列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
if include_history and streamer_id:
|
|
|
|
|
|
# 获取指定主播的所有宝箱(包括历史)
|
|
|
|
|
|
chests = GameService.get_chests_by_streamer(db, streamer_id)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 默认行为:获取活跃宝箱
|
|
|
|
|
|
chests = GameService.get_active_chests(db, streamer_id)
|
|
|
|
|
|
|
|
|
|
|
|
# 为每个宝箱计算剩余时间并转换为响应模型
|
|
|
|
|
|
response_chests = []
|
|
|
|
|
|
for chest in chests:
|
|
|
|
|
|
# 计算time_remaining
|
|
|
|
|
|
time_remaining = 0
|
|
|
|
|
|
if chest.status == ChestStatus.BETTING:
|
|
|
|
|
|
# 使用本地时间进行计算(与数据库func.now()保持一致)
|
|
|
|
|
|
if isinstance(chest.created_at, str):
|
|
|
|
|
|
if 'T' in chest.created_at:
|
|
|
|
|
|
created_time = datetime.fromisoformat(chest.created_at.replace('Z', ''))
|
|
|
|
|
|
else:
|
|
|
|
|
|
created_time = datetime.strptime(chest.created_at, '%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
else:
|
|
|
|
|
|
created_time = chest.created_at.replace(tzinfo=None) if chest.created_at and chest.created_at.tzinfo else chest.created_at
|
|
|
|
|
|
|
|
|
|
|
|
# 使用本地时间进行计算(func.now()返回的是服务器本地时间)
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
elapsed = (now - created_time).total_seconds()
|
|
|
|
|
|
time_remaining = max(0, int(chest.countdown_seconds - elapsed))
|
|
|
|
|
|
print(f'Chest {chest.id}: created_at={created_time}, now_local={now}, elapsed={elapsed}, time_remaining={time_remaining}')
|
|
|
|
|
|
|
|
|
|
|
|
# 转换为响应模型
|
|
|
|
|
|
chest_response = ChestResponse(
|
|
|
|
|
|
id=chest.id,
|
|
|
|
|
|
streamer_id=chest.streamer_id,
|
|
|
|
|
|
title=chest.title,
|
|
|
|
|
|
option_a=chest.option_a,
|
|
|
|
|
|
option_b=chest.option_b,
|
|
|
|
|
|
status=chest.status.value if hasattr(chest.status, 'value') else chest.status,
|
|
|
|
|
|
pool_a=chest.pool_a,
|
|
|
|
|
|
pool_b=chest.pool_b,
|
|
|
|
|
|
total_bets=chest.total_bets,
|
|
|
|
|
|
countdown_seconds=chest.countdown_seconds,
|
|
|
|
|
|
time_remaining=time_remaining,
|
|
|
|
|
|
locked_at=chest.locked_at,
|
|
|
|
|
|
settled_at=chest.settled_at,
|
|
|
|
|
|
result=chest.result,
|
|
|
|
|
|
created_at=chest.created_at
|
|
|
|
|
|
)
|
|
|
|
|
|
response_chests.append(chest_response)
|
|
|
|
|
|
|
|
|
|
|
|
return response_chests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/chests/{chest_id}", response_model=ChestResponse)
|
|
|
|
|
|
def get_chest(
|
|
|
|
|
|
chest_id: int,
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取宝箱详情
|
|
|
|
|
|
"""
|
|
|
|
|
|
chest = GameService.get_chest_by_id(db, chest_id)
|
|
|
|
|
|
if not chest:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="Chest not found")
|
|
|
|
|
|
|
|
|
|
|
|
# 计算剩余时间
|
|
|
|
|
|
time_remaining = 0
|
|
|
|
|
|
if chest.status == ChestStatus.BETTING:
|
|
|
|
|
|
# 使用本地时间进行计算(与数据库func.now()保持一致)
|
|
|
|
|
|
if isinstance(chest.created_at, str):
|
|
|
|
|
|
if 'T' in chest.created_at:
|
|
|
|
|
|
created_time = datetime.fromisoformat(chest.created_at.replace('Z', ''))
|
|
|
|
|
|
else:
|
|
|
|
|
|
created_time = datetime.strptime(chest.created_at, '%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
else:
|
|
|
|
|
|
created_time = chest.created_at.replace(tzinfo=None) if chest.created_at and chest.created_at.tzinfo else chest.created_at
|
|
|
|
|
|
|
|
|
|
|
|
# 使用本地时间进行计算(func.now()返回的是服务器本地时间)
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
elapsed = (now - created_time).total_seconds()
|
|
|
|
|
|
time_remaining = max(0, int(chest.countdown_seconds - elapsed))
|
|
|
|
|
|
print(f"Single chest {chest.id}: created_at={created_time}, now_local={now}, elapsed={elapsed}, time_remaining={time_remaining}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
time_remaining = 0
|
|
|
|
|
|
|
|
|
|
|
|
# 返回包含time_remaining的响应
|
|
|
|
|
|
return ChestResponse(
|
|
|
|
|
|
id=chest.id,
|
|
|
|
|
|
streamer_id=chest.streamer_id,
|
|
|
|
|
|
title=chest.title,
|
|
|
|
|
|
option_a=chest.option_a,
|
|
|
|
|
|
option_b=chest.option_b,
|
|
|
|
|
|
status=chest.status.value if hasattr(chest.status, 'value') else chest.status,
|
|
|
|
|
|
pool_a=chest.pool_a,
|
|
|
|
|
|
pool_b=chest.pool_b,
|
|
|
|
|
|
total_bets=chest.total_bets,
|
|
|
|
|
|
countdown_seconds=chest.countdown_seconds,
|
|
|
|
|
|
time_remaining=time_remaining,
|
|
|
|
|
|
locked_at=chest.locked_at,
|
|
|
|
|
|
settled_at=chest.settled_at,
|
|
|
|
|
|
result=chest.result,
|
|
|
|
|
|
created_at=chest.created_at
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/bet", response_model=BetResponse)
|
|
|
|
|
|
def place_bet(
|
|
|
|
|
|
bet_data: BetCreate,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_user = Depends(get_current_user)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
下注
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
return GameService.place_bet(db, current_user, bet_data)
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/chests/{chest_id}/lock")
|
|
|
|
|
|
def lock_chest(
|
|
|
|
|
|
chest_id: int,
|
|
|
|
|
|
lock_data: ChestLock,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
封盘(仅主播)
|
|
|
|
|
|
"""
|
|
|
|
|
|
chest = GameService.get_chest_by_id(db, chest_id)
|
|
|
|
|
|
if not chest:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="Chest not found")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
GameService.lock_chest(db, chest, current_user)
|
|
|
|
|
|
return {"message": "封盘成功"}
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/chests/{chest_id}/settle")
|
|
|
|
|
|
def settle_chest(
|
|
|
|
|
|
chest_id: int,
|
|
|
|
|
|
settle_data: ChestSettle,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
结算宝箱(仅主播)
|
|
|
|
|
|
"""
|
|
|
|
|
|
chest = GameService.get_chest_by_id(db, chest_id)
|
|
|
|
|
|
if not chest:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="Chest not found")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
GameService.settle_chest(db, chest, settle_data.result, current_user)
|
|
|
|
|
|
return {"message": "结算成功"}
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/chests/{chest_id}/bets", response_model=List[BetResponse])
|
|
|
|
|
|
def get_chest_bets(
|
|
|
|
|
|
chest_id: int,
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取宝箱下注记录
|
|
|
|
|
|
"""
|
|
|
|
|
|
return GameService.get_chest_bets(db, chest_id)
|
2025-12-18 15:03:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 宝箱模板相关路由 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/templates", response_model=List[ChestTemplateResponse])
|
|
|
|
|
|
def get_templates(
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取当前主播的宝箱模板列表
|
|
|
|
|
|
"""
|
|
|
|
|
|
templates = GameService.get_templates_by_streamer(db, current_user.id)
|
|
|
|
|
|
return templates
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/templates", response_model=ChestTemplateResponse)
|
|
|
|
|
|
def create_template(
|
|
|
|
|
|
template_data: ChestTemplateCreate,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
创建宝箱模板
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
template = GameService.create_template(db, current_user.id, template_data)
|
|
|
|
|
|
return template
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/templates/{template_id}", response_model=ChestTemplateResponse)
|
|
|
|
|
|
def update_template(
|
|
|
|
|
|
template_id: int,
|
|
|
|
|
|
template_data: ChestTemplateUpdate,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
更新宝箱模板
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
template = GameService.update_template(db, template_id, current_user.id, template_data)
|
|
|
|
|
|
return template
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/templates/{template_id}")
|
|
|
|
|
|
def delete_template(
|
|
|
|
|
|
template_id: int,
|
|
|
|
|
|
db: Session = Depends(get_db),
|
|
|
|
|
|
current_user: get_current_streamer = Depends(get_current_streamer)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""
|
|
|
|
|
|
删除宝箱模板
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
GameService.delete_template(db, template_id, current_user.id)
|
|
|
|
|
|
return {"message": "删除成功"}
|
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|