第三版

This commit is contained in:
taiyi 2025-12-18 13:28:29 +08:00
parent eb11867a98
commit 4927420266
19 changed files with 331 additions and 1106 deletions

2
.env
View File

@ -12,7 +12,7 @@
DB_POOL_SIZE=20
DB_MAX_OVERFLOW=30
DB_POOL_TIMEOUT=30
DB_POOL_RECYCLE=3600
DB_POOL_RECYCLE=1800
# 覆盖默认配置
DATABASE_URL=mysql+pymysql://root:taiyi1224@localhost:3306/baoxiang

View File

@ -1,130 +0,0 @@
# 🚀 快速启动指南
## 一键启动(推荐)
使用 Docker Compose 一键启动所有服务:
```bash
# 启动所有服务MySQL、Redis、后端、前端
docker-compose up -d
# 查看服务状态
docker-compose ps
# 查看日志
docker-compose logs -f backend
```
启动后访问:
- **前端界面**: http://localhost:3000
- **后端 API**: http://localhost:8000
- **API 文档**: http://localhost:8000/docs
---
## 分别启动
### 1. 后端服务
```bash
# 进入后端目录
cd backend
# 安装依赖
pip install -r requirements.txt
# 启动服务
python run.py
```
### 2. 前端服务
```bash
# 进入前端目录
cd frontend
# 安装依赖
npm install
# 启动开发服务器
npm run dev
```
---
## 📋 系统要求
- **Docker**: 20.10+
- **Docker Compose**: 2.0+
- **Python**: 3.10+
- **Node.js**: 16+
---
## 🔧 配置说明
### 环境变量
复制环境变量示例文件:
```bash
cp .env.example .env
```
主要配置项:
- `DATABASE_URL`: MySQL 连接地址
- `SECRET_KEY`: JWT 密钥
- `REDIS_HOST`: Redis 主机地址
### MySQL 连接信息
通过 Docker 启动时:
```
主机: localhost
端口: 3306
数据库: treasure_box_game
用户名: root
密码: treasure_box_2024
```
---
## 🐛 常见问题
### Q: Docker 启动失败?
A: 检查端口占用情况:
```bash
# Windows
netstat -ano | findstr :3306
# Linux/Mac
lsof -i :3306
```
### Q: 数据库连接失败?
A: 确保 MySQL 容器已启动并健康:
```bash
docker-compose ps
docker-compose logs mysql
```
### Q: 前端无法连接后端?
A: 检查 Vite 代理配置和后端 CORS 设置
---
## 📚 更多文档
- **完整迁移指南**: `MYSQL_迁移指南.md`
- **项目完成报告**: `项目完成报告.md`
- **前端开发文档**: `frontend/README.md`
- **后端 API 文档**: http://localhost:8000/docs
---
## 🎯 下一步
1. 访问 http://localhost:3000 注册用户
2. 体验完整的竞猜流程
3. 尝试创建宝箱(需要 streamer 权限)
4. 查看 API 文档了解所有接口
**祝您使用愉快!** 🎉

View File

@ -1,57 +1,17 @@
# ========================================
# 数据库配置 (MySQL)
# ========================================
# MySQL连接格式: mysql+pymysql://username:password@host:port/database_name
DATABASE_URL=mysql+pymysql://root:taiyi1224@localhost:3306/baoxiang
# 这是一个示例.env文件用于说明环境变量的格式
# 在生产环境中,请使用环境变量而不是这个文件
# SQLite开发配置 (可选)
# SQLite连接格式: sqlite:///./database.db
# DATABASE_URL=sqlite:///./treasure_box_game.db
# 数据库连接池配置
# 数据库配置
DB_DATABASE_URL=mysql+pymysql://root:taiyi1224@199.68.217.236:3306/baoxiang
DB_POOL_SIZE=20
DB_MAX_OVERFLOW=30
DB_POOL_TIMEOUT=30
DB_POOL_RECYCLE=3600
# ========================================
# JWT配置
# ========================================
# 安全配置
SECRET_KEY=qwer123
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=10080
# ========================================
# Redis配置
# ========================================
REDIS_HOST=localhost
REDIS_HOST=199.68.217.236
REDIS_PORT=6379
REDIS_DB=0
REDIS_PASSWORD=
# ========================================
# 游戏配置
# ========================================
NEW_USER_REWARD=100000
DAILY_ALLOWANCE=5000
ALLOWANCE_THRESHOLD=1000
# ========================================
# 抽水配置
# ========================================
HOUSE_EDGE=0.10
STREAMER_SHARE=0.05
PLATFORM_SHARE=0.05
# ========================================
# MySQL Docker配置说明
# ========================================
# 如果使用docker-compose中的MySQL服务连接地址为:
# DATABASE_URL=mysql+pymysql://root:treasure_box_2024@mysql:3306/treasure_box_game
#
# MySQL容器配置:
# - 用户名: root
# - 密码: treasure_box_2024
# - 数据库: treasure_box_game
# - 端口: 3306
# 服务器配置
HOST=0.0.0.0
PORT=8000

View File

@ -6,65 +6,64 @@ from pydantic_settings import BaseSettings
from pydantic import ConfigDict
from typing import Optional, List
from functools import lru_cache
import os
class ServerSettings(BaseSettings):
"""服务器配置"""
model_config = ConfigDict(env_prefix="SERVER_", env_file="D:\work\code\python\demo\demo07\backend\.env")
HOST: str = "0.0.0.0"
PORT: int = 8000
WORKERS: int = 1
LOG_LEVEL: str = "info"
class Config:
env_prefix = "SERVER_"
class DatabaseSettings(BaseSettings):
"""数据库配置"""
model_config = ConfigDict(env_prefix="DB_", env_file="D:\work\code\python\demo\demo07\backend\.env")
# MySQL连接格式: mysql+pymysql://username:password@host:port/database_name?charset=utf8mb4
DATABASE_URL: str = "mysql+pymysql://root:taiyi1224@localhost:3306/baoxiang?charset=utf8mb4"
# SQLite开发配置 (可选)
# SQLite连接格式: sqlite:///./database.db
# DATABASE_URL: str = "sqlite:///./treasure_box_game.db"
# 数据库连接池配置
DB_POOL_SIZE: int = 20
DB_MAX_OVERFLOW: int = 30
DB_POOL_TIMEOUT: int = 30
DB_POOL_RECYCLE: int = 3600
class Config:
env_prefix = "DB_"
class SecuritySettings(BaseSettings):
"""安全配置"""
model_config = ConfigDict(env_prefix="SECURITY_", env_file="D:\work\code\python\demo\demo07\backend\.env")
SECRET_KEY: str = "your-secret-key-change-in-production"
ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080 # 7天
PASSWORD_MAX_LENGTH: int = 72 # 密码哈希最大长度
WS_CLOSE_CODE: int = 4001 # WebSocket关闭码
class Config:
env_prefix = "SECURITY_"
class RedisSettings(BaseSettings):
"""Redis配置"""
model_config = ConfigDict(env_prefix="REDIS_", env_file="D:\work\code\python\demo\demo07\backend\.env")
REDIS_HOST: str = "localhost"
REDIS_PORT: int = 6379
REDIS_DB: int = 0
REDIS_PASSWORD: Optional[str] = None
REDIS_TIMEOUT: int = 5
class Config:
env_prefix = "REDIS_"
class CORSSettings(BaseSettings):
"""CORS跨域配置"""
ALLOW_ORIGINS: str = "http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001,*"
model_config = ConfigDict(env_prefix="CORS_", env_file="D:\work\code\python\demo\demo07\backend\.env")
ALLOW_ORIGINS: str = "http://localhost:3000,http://127.0.0.1:3000,http://localhost:3001,http://127.0.0.1:3001,http://bx.youxidaren.cn,https://bx.youxidaren.cn,http://199.68.217.236:3000,http://199.68.217.236:3001,http://199.68.217.236:8000,*"
ALLOW_CREDENTIALS: bool = True
ALLOW_METHODS: str = "GET,POST,PUT,DELETE,PATCH,OPTIONS"
ALLOW_HEADERS: str = "Authorization,Content-Type,Accept,X-Requested-With"
@ -81,12 +80,11 @@ class CORSSettings(BaseSettings):
"""解析允许的头部列表"""
return [header.strip() for header in self.ALLOW_HEADERS.split(",")]
class Config:
env_prefix = "CORS_"
class PaginationSettings(BaseSettings):
"""分页配置"""
model_config = ConfigDict(env_prefix="PAGINATION_", env_file="D:\work\code\python\demo\demo07\backend\.env")
DEFAULT_PAGE_SIZE: int = 20
MAX_PAGE_SIZE: int = 100
ADMIN_PAGE_SIZE: int = 20
@ -95,12 +93,11 @@ class PaginationSettings(BaseSettings):
USER_LIST_LIMIT: int = 100
STREAMER_LIST_LIMIT: int = 100
class Config:
env_prefix = "PAGINATION_"
class GameSettings(BaseSettings):
"""游戏配置"""
model_config = ConfigDict(env_prefix="GAME_", env_file="D:\work\code\python\demo\demo07\backend\.env")
# 经济配置
NEW_USER_REWARD: int = 100000 # 新用户注册奖励(分)
DAILY_ALLOWANCE: int = 5000 # 每日低保(分)
@ -120,22 +117,20 @@ class GameSettings(BaseSettings):
MAX_COMMISSION_RATE: float = 100.0 # 最大主播抽成(%)
DEFAULT_COMMISSION_RATE: float = 5.0 # 默认主播抽成(%)
class Config:
env_prefix = "GAME_"
class WebSocketSettings(BaseSettings):
"""WebSocket配置"""
model_config = ConfigDict(env_prefix="WS_", env_file="D:\work\code\python\demo\demo07\backend\.env")
BROADCAST_INTERVAL: int = 200 # 广播间隔(毫秒)
HEARTBEAT_INTERVAL: int = 30 # 心跳间隔(秒)
CONNECTION_TIMEOUT: int = 60 # 连接超时(秒)
class Config:
env_prefix = "WS_"
class AdminSettings(BaseSettings):
"""管理员配置"""
model_config = ConfigDict(env_prefix="ADMIN_", env_file="D:\work\code\python\demo\demo07\backend\.env")
DEFAULT_ADMIN_PASSWORD: str = "admin123"
DEFAULT_STREAMER_PASSWORD: str = "streamer123"
DEFAULT_USER_PASSWORD: str = "user123"
@ -143,26 +138,22 @@ class AdminSettings(BaseSettings):
DEFAULT_STREAMER_BALANCE: int = 500000 # 默认主播余额(分)
DEFAULT_USER_BALANCE: int = 200000 # 默认用户余额(分)
class Config:
env_prefix = "ADMIN_"
class AnnouncementSettings(BaseSettings):
"""公告配置"""
model_config = ConfigDict(env_prefix="ANNOUNCEMENT_", env_file="D:\work\code\python\demo\demo07\backend\.env")
DEFAULT_PRIORITY: int = 0
MAX_PRIORITY: int = 100
HIGH_PRIORITY: int = 100
class Config:
env_prefix = "ANNOUNCEMENT_"
class Settings(BaseSettings):
"""
根配置类 - 组合所有子配置
"""
model_config = ConfigDict(
env_file=".env",
env_file="./.env",
extra='ignore' # 忽略.env文件中未定义的变量
)

View File

@ -27,10 +27,18 @@ else:
max_overflow=db_settings.DB_MAX_OVERFLOW,
pool_timeout=db_settings.DB_POOL_TIMEOUT,
pool_recycle=db_settings.DB_POOL_RECYCLE,
pool_pre_ping=True, # 每次使用连接前检测是否有效,防止使用已断开的连接
echo=False,
future=True,
# 设置连接时使用的字符集
connect_args={"charset": "utf8mb4", "collation": "utf8mb4_unicode_ci"}
connect_args={
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci",
# 添加连接超时设置
"connect_timeout": 60,
"read_timeout": 60,
"write_timeout": 60
}
)
# 如果是MySQL启用严格模式
@ -60,8 +68,29 @@ def get_db():
db = SessionLocal()
try:
yield db
except Exception as e:
# 如果发生异常,先回滚未提交的事务
try:
if db.is_active:
db.rollback()
except Exception:
pass
raise e
finally:
db.close()
# 安全地关闭数据库连接
try:
# 检查会话状态
if db.is_active:
db.close()
else:
# 如果会话已经不活跃,尝试无效化连接
try:
db.close()
except Exception:
pass
except Exception:
# 忽略关闭时的错误,避免掩盖原始异常
pass
def create_tables():

View File

@ -26,6 +26,7 @@ def create_chest(
"""
创建宝箱仅主播
"""
print(f"[DEBUG] Creating chest: user={current_user.id}, role={current_user.role}, data={chest_data}")
try:
chest = GameService.create_chest(db, current_user, chest_data)
@ -66,6 +67,10 @@ def create_chest(
created_at=chest.created_at
)
except ValueError as e:
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}")
raise HTTPException(status_code=400, detail=str(e))

View File

@ -330,13 +330,17 @@ class GameService:
return True
except Exception as e:
db.rollback()
# 确保在异常时正确回滚
try:
db.rollback()
except Exception:
pass
raise e
@staticmethod
def _settle_winner(db: Session, chest: Chest, winner: str) -> None:
"""
结算获胜方
结算获胜方 - 简化版本避免数据库连接问题
"""
# 获取所有下注
bets = db.query(Bet).filter(
@ -355,130 +359,173 @@ class GameService:
platform_share = ConfigManager.get_platform_share(db)
streamer_income = int(total_pool * streamer_share)
platform_income = int(total_pool * platform_share)
distributable = total_pool - streamer_income - platform_income
# 准备结算数据
settlement_data = {
'streamer_income': streamer_income,
'platform_income': platform_income,
'settlement_results': [],
'admin_user_info': None,
'streamer_user_info': None
}
# 主播分润
streamer = db.query(User).filter(User.id == chest.streamer_id).first()
if streamer:
if streamer and streamer_income > 0:
old_balance = streamer.balance
streamer.balance += streamer_income
db.commit()
settlement_data['streamer_user_info'] = {
'user_id': streamer.id,
'old_balance': old_balance,
'new_balance': streamer.balance,
'amount': streamer_income
}
from ..services.user_service import UserService
UserService.create_transaction(
db=db,
user_id=streamer.id,
transaction_type="抽水分润",
amount=streamer_income,
balance_after=streamer.balance,
related_id=chest.id,
description=f"宝箱《{chest.title}》抽水分润"
)
# 平台抽水 - 获取管理员信息但不更新
admin_user = None
if platform_income > 0:
try:
admin_user = db.query(User).filter(User.role == "admin").first()
if admin_user:
old_balance = admin_user.balance
admin_user.balance += platform_income
settlement_data['admin_user_info'] = {
'user_id': admin_user.id,
'old_balance': old_balance,
'new_balance': admin_user.balance,
'amount': platform_income
}
except Exception as e:
print(f"获取管理员用户失败: {str(e)}")
admin_user = None
# 平台抽水 - 直接到管理员账户
try:
admin_user = UserService.get_admin_user(db)
if admin_user and platform_income > 0:
# 记录转账前的余额
old_balance = admin_user.balance
admin_user.balance += platform_income
db.commit()
try:
UserService.create_transaction(
db=db,
user_id=admin_user.id,
transaction_type="平台抽水",
amount=platform_income,
balance_after=admin_user.balance,
related_id=chest.id,
description=f"宝箱《{chest.title}》平台抽水收益"
)
except Exception as e:
print(f"创建平台抽水交易记录失败: {str(e)}")
# 即使交易记录创建失败,也不应回滚余额变更
# 可以在这里添加告警通知或其他补偿措施
# 通知管理员余额更新
import asyncio
try:
# 尝试获取当前事件循环
loop = asyncio.get_running_loop()
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
loop.create_task(notify_user_balance_update(admin_user.id, admin_user.balance))
except RuntimeError:
# 如果没有运行中的事件循环,则在新线程中处理
import threading
def run_async():
async def _run():
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
await notify_user_balance_update(admin_user.id, admin_user.balance)
asyncio.run(_run())
thread = threading.Thread(target=run_async, daemon=True)
thread.start()
print(f"平台抽水转账成功: {platform_income} 分至管理员账户 {admin_user.id}")
except Exception as e:
# 如果转账失败,记录错误并回滚
db.rollback()
print(f"平台抽水转账失败: {str(e)}")
# 可以在这里添加更详细的错误处理逻辑,比如发送告警通知
# 计算赔率 - 修改为只对获胜方抽水
# 计算赔率 - 正确的赔率计算
winner_pool = chest.pool_a if winner == "A" else chest.pool_b
loser_pool = chest.pool_b if winner == "A" else chest.pool_a
if winner_pool > 0:
# 赔率计算改为:(获胜方奖池 + 失败方奖池 * 0.9) / 获胜方奖池
odds = (winner_pool + loser_pool * 0.9) / winner_pool
# 赔率计算失败方奖池的90%按比例分配给获胜方用户
odds = 1 + (loser_pool * 0.9 / winner_pool)
# 结算给获胜方
for bet in bets:
if bet.option == winner:
payout = int(bet.amount * odds)
bet.payout = payout
bet.status = "WON"
# 给用户加钱
user = db.query(User).filter(User.id == bet.user_id).first()
if user:
user.balance += payout
db.commit()
# 通知用户余额更新
import asyncio
try:
# 尝试获取当前事件循环
loop = asyncio.get_running_loop()
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
loop.create_task(notify_user_balance_update(user.id, user.balance))
except RuntimeError:
# 如果没有运行中的事件循环,则在新线程中处理
import threading
def run_async():
async def _run():
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
await notify_user_balance_update(user.id, user.balance)
asyncio.run(_run())
thread = threading.Thread(target=run_async, daemon=True)
thread.start()
# 计算奖金
payout = int(bet.amount * odds)
bet.payout = payout
bet.status = "WON"
# 记录交易
UserService.create_transaction(
db=db,
user_id=user.id,
transaction_type="获胜",
amount=payout,
balance_after=user.balance,
related_id=chest.id,
description=f"宝箱《{chest.title}》获胜奖金"
)
# 更新用户余额
old_balance = user.balance
user.balance += payout
settlement_data['settlement_results'].append({
'bet_id': bet.id,
'user_id': user.id,
'old_balance': old_balance,
'new_balance': user.balance,
'payout': payout,
'success': True
})
else:
bet.status = "WON"
bet.payout = 0
settlement_data['settlement_results'].append({
'bet_id': bet.id,
'user_id': bet.user_id,
'error': '用户不存在',
'success': False
})
else:
bet.status = "LOST"
db.commit()
# 一次性提交所有变更
db.commit()
# 创建交易记录和发送通知(在提交后进行)
from ..services.user_service import UserService
# 主播交易记录
if settlement_data['streamer_user_info']:
try:
info = settlement_data['streamer_user_info']
UserService.create_transaction(
db=db,
user_id=info['user_id'],
transaction_type="抽水分润",
amount=info['amount'],
balance_after=info['new_balance'],
related_id=chest.id,
description=f"宝箱《{chest.title}》抽水分润"
)
# 发送通知
GameService._send_balance_notification(info['user_id'], info['new_balance'])
except Exception as e:
print(f"创建主播分润交易记录失败: {str(e)}")
# 管理员交易记录
if settlement_data['admin_user_info']:
try:
info = settlement_data['admin_user_info']
UserService.create_transaction(
db=db,
user_id=info['user_id'],
transaction_type="平台抽水",
amount=info['amount'],
balance_after=info['new_balance'],
related_id=chest.id,
description=f"宝箱《{chest.title}》平台抽水收益"
)
# 发送通知
GameService._send_balance_notification(info['user_id'], info['new_balance'])
except Exception as e:
print(f"创建平台抽水交易记录失败: {str(e)}")
# 用户交易记录
for result in settlement_data['settlement_results']:
if result['success']:
try:
UserService.create_transaction(
db=db,
user_id=result['user_id'],
transaction_type="获胜",
amount=result['payout'],
balance_after=result['new_balance'],
related_id=chest.id,
description=f"宝箱《{chest.title}》获胜奖金"
)
# 发送通知
GameService._send_balance_notification(result['user_id'], result['new_balance'])
except Exception as e:
print(f"创建用户获胜交易记录失败 - 用户ID: {result['user_id']}, 错误: {str(e)}")
@staticmethod
def _send_balance_notification(user_id: int, new_balance: int) -> None:
"""
发送余额更新通知
"""
try:
import asyncio
try:
# 尝试获取当前事件循环
loop = asyncio.get_running_loop()
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
loop.create_task(notify_user_balance_update(user_id, new_balance))
except RuntimeError:
# 如果没有运行中的事件循环,则在新线程中处理
import threading
def run_async():
async def _run():
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
await notify_user_balance_update(user_id, new_balance)
asyncio.run(_run())
thread = threading.Thread(target=run_async, daemon=True)
thread.start()
except Exception as e:
print(f"发送余额通知失败 - 用户ID: {user_id}, 错误: {str(e)}")
@staticmethod
def _refund_chest(db: Session, chest: Chest) -> None:
@ -490,55 +537,78 @@ class GameService:
Bet.status == "PENDING"
).all()
refund_results = []
for bet in bets:
# 退款给用户
user = db.query(User).filter(User.id == bet.user_id).first()
if user:
user.balance += bet.amount
db.commit()
# 通知用户余额更新
import asyncio
try:
# 尝试获取当前事件循环
loop = asyncio.get_running_loop()
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
loop.create_task(notify_user_balance_update(user.id, user.balance))
except RuntimeError:
# 如果没有运行中的事件循环,则在新线程中处理
import threading
def run_async():
async def _run():
# 局部导入避免循环导入
from ..routers.websocket import notify_user_balance_update
await notify_user_balance_update(user.id, user.balance)
asyncio.run(_run())
thread = threading.Thread(target=run_async, daemon=True)
thread.start()
# 记录交易
from ..services.user_service import UserService
UserService.create_transaction(
db=db,
user_id=user.id,
transaction_type="退款",
amount=bet.amount,
balance_after=user.balance,
related_id=chest.id,
description=f"宝箱《{chest.title}》流局退款"
)
bet.status = "REFUNDED"
try:
# 退款给用户
user = db.query(User).filter(User.id == bet.user_id).first()
if user:
old_balance = user.balance
user.balance += bet.amount
bet.status = "REFUNDED"
refund_results.append({
'bet_id': bet.id,
'user_id': user.id,
'old_balance': old_balance,
'new_balance': user.balance,
'refund_amount': bet.amount,
'success': True
})
else:
bet.status = "REFUNDED"
refund_results.append({
'bet_id': bet.id,
'user_id': bet.user_id,
'error': '用户不存在',
'success': False
})
except Exception as e:
# 记录错误但不中断其他用户的退款
print(f"退款失败 - 下注ID: {bet.id}, 错误: {str(e)}")
bet.status = "REFUNDED"
refund_results.append({
'bet_id': bet.id,
'user_id': bet.user_id,
'error': str(e),
'success': False
})
# 重置奖池
chest.pool_a = 0
chest.pool_b = 0
# 一次性提交所有变更
db.commit()
# 更新缓存
update_pool_cache(chest.id, 0, 0)
# 创建交易记录和发送通知(在提交后进行)
for result in refund_results:
if result['success']:
user_id = result['user_id']
refund_amount = result['refund_amount']
new_balance = result['new_balance']
# 发送通知
GameService._send_balance_notification(user_id, new_balance)
# 记录交易
try:
from ..services.user_service import UserService
UserService.create_transaction(
db=db,
user_id=user_id,
transaction_type="退款",
amount=refund_amount,
balance_after=new_balance,
related_id=chest.id,
description=f"宝箱《{chest.title}》流局退款"
)
except Exception as e:
print(f"创建交易记录失败 - 用户ID: {user_id}, 错误: {str(e)}")
# 即使交易记录创建失败,也不回滚余额(因为已经提交)
@staticmethod
def get_chest_bets(db: Session, chest_id: int) -> List[Bet]:
"""

View File

@ -1,356 +0,0 @@
/*
Navicat Premium Dump SQL
Source Server :
Source Server Type : MySQL
Source Server Version : 80406 (8.4.6)
Source Host : localhost:3306
Source Schema : baoxiang
Target Server Type : MySQL
Target Server Version : 80406 (8.4.6)
File Encoding : 65001
Date: 15/12/2025 17:44:49
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for announcements
-- ----------------------------
DROP TABLE IF EXISTS `announcements`;
CREATE TABLE `announcements` (
`id` int NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '公告标题',
`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '公告内容',
`type` enum('INFO','WARNING','PRIZE') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '公告类型',
`is_pinned` tinyint(1) NULL DEFAULT NULL COMMENT '是否置顶',
`priority` int NULL DEFAULT NULL COMMENT '优先级(数字越大优先级越高)',
`starts_at` datetime NULL DEFAULT NULL COMMENT '生效时间',
`expires_at` datetime NULL DEFAULT NULL COMMENT '过期时间',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
`updated_at` datetime NULL DEFAULT (now()) COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `ix_announcements_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of announcements
-- ----------------------------
INSERT INTO `announcements` VALUES (1, 'c', 'a', 'WARNING', 1, 0, NULL, NULL, '2025-12-13 08:16:37', '2025-12-13 08:16:37');
INSERT INTO `announcements` VALUES (3, 'dfa', 'sfasd', 'WARNING', 1, 0, NULL, NULL, '2025-12-14 03:16:04', '2025-12-14 03:16:04');
-- ----------------------------
-- Table structure for bets
-- ----------------------------
DROP TABLE IF EXISTS `bets`;
CREATE TABLE `bets` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL COMMENT '用户ID',
`chest_id` int NOT NULL COMMENT '宝箱ID',
`option` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '下注选项',
`amount` bigint NOT NULL COMMENT '下注金额(分)',
`payout` bigint NULL DEFAULT NULL COMMENT '获奖金额(分)',
`status` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '状态',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `ix_bets_chest_id`(`chest_id` ASC) USING BTREE,
INDEX `ix_bets_id`(`id` ASC) USING BTREE,
INDEX `ix_bets_user_id`(`user_id` ASC) USING BTREE,
CONSTRAINT `bets_ibfk_1` FOREIGN KEY (`chest_id`) REFERENCES `chests` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of bets
-- ----------------------------
INSERT INTO `bets` VALUES (1, 7, 3, 'A', 500, 0, 'PENDING', '2025-12-14 04:35:46');
INSERT INTO `bets` VALUES (2, 9, 3, 'A', 2300, 0, 'PENDING', '2025-12-14 04:39:06');
INSERT INTO `bets` VALUES (3, 9, 3, 'B', 2500, 0, 'PENDING', '2025-12-14 04:44:04');
INSERT INTO `bets` VALUES (4, 9, 4, 'A', 200, 180, 'WON', '2025-12-14 05:15:13');
INSERT INTO `bets` VALUES (5, 9, 6, 'A', 3000, 0, 'LOST', '2025-12-14 05:23:54');
INSERT INTO `bets` VALUES (6, 9, 6, 'B', 1000, 3600, 'WON', '2025-12-14 05:23:58');
INSERT INTO `bets` VALUES (7, 10, 3, 'A', 1100, 0, 'PENDING', '2025-12-14 05:26:08');
INSERT INTO `bets` VALUES (8, 9, 9, 'A', 100, 0, 'PENDING', '2025-12-14 06:43:42');
INSERT INTO `bets` VALUES (9, 9, 10, 'A', 500, 0, 'LOST', '2025-12-14 10:16:39');
INSERT INTO `bets` VALUES (10, 9, 10, 'B', 1000, 1350, 'WON', '2025-12-14 10:17:11');
INSERT INTO `bets` VALUES (11, 9, 11, 'A', 1000, 0, 'PENDING', '2025-12-14 10:19:24');
INSERT INTO `bets` VALUES (12, 9, 12, 'A', 500, 450, 'WON', '2025-12-14 10:39:53');
INSERT INTO `bets` VALUES (13, 9, 13, 'B', 500, 450, 'WON', '2025-12-14 10:56:23');
INSERT INTO `bets` VALUES (14, 9, 14, 'A', 10911, 18700, 'WON', '2025-12-14 11:03:29');
INSERT INTO `bets` VALUES (15, 9, 14, 'B', 9865, 0, 'LOST', '2025-12-14 11:03:38');
INSERT INTO `bets` VALUES (16, 9, 17, 'A', 100, 0, 'PENDING', '2025-12-14 11:23:25');
-- ----------------------------
-- Table structure for chests
-- ----------------------------
DROP TABLE IF EXISTS `chests`;
CREATE TABLE `chests` (
`id` int NOT NULL AUTO_INCREMENT,
`streamer_id` int NOT NULL COMMENT '主播ID',
`title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '宝箱标题',
`option_a` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '选项A',
`option_b` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '选项B',
`status` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '宝箱状态',
`pool_a` bigint NULL DEFAULT NULL COMMENT 'A边奖池(分)',
`pool_b` bigint NULL DEFAULT NULL COMMENT 'B边奖池(分)',
`total_bets` int NULL DEFAULT NULL COMMENT '总下注次数',
`countdown_seconds` int NULL DEFAULT NULL COMMENT '倒计时(秒)',
`locked_at` datetime NULL DEFAULT NULL COMMENT '封盘时间',
`settled_at` datetime NULL DEFAULT NULL COMMENT '结算时间',
`result` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '结算结果',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `ix_chests_streamer_id`(`streamer_id` ASC) USING BTREE,
INDEX `ix_chests_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 22 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of chests
-- ----------------------------
INSERT INTO `chests` VALUES (3, 7, 'sdaf', 'asdf', 'adf', 'BETTING', 3900, 2500, 11, 298, NULL, NULL, NULL, '2025-12-14 04:32:51');
INSERT INTO `chests` VALUES (4, 10, 'ad', '1', '2', 'FINISHED', 200, 0, 2, 300, '2025-12-14 13:15:24', '2025-12-14 13:36:59', 'A', '2025-12-14 05:13:40');
INSERT INTO `chests` VALUES (5, 10, '123', '1', '1', 'FINISHED', 0, 0, 0, 300, '2025-12-14 13:22:59', '2025-12-14 13:36:54', 'A', '2025-12-14 05:22:55');
INSERT INTO `chests` VALUES (6, 10, '123', '1', '1', 'FINISHED', 3000, 1000, 5, 300, '2025-12-14 13:24:45', '2025-12-14 13:36:43', 'B', '2025-12-14 05:23:30');
INSERT INTO `chests` VALUES (7, 10, '`12', '1', '2', 'FINISHED', 0, 0, 0, 300, '2025-12-14 14:38:28', '2025-12-14 14:38:32', 'B', '2025-12-14 06:37:37');
INSERT INTO `chests` VALUES (8, 10, '123', '1', '12', 'FINISHED', 0, 0, 0, 60, '2025-12-14 14:42:30', '2025-12-14 14:42:35', 'B', '2025-12-14 06:38:40');
INSERT INTO `chests` VALUES (9, 10, '123', '1', '12', 'FINISHED', 100, 0, 1, 60, '2025-12-14 18:15:45', '2025-12-14 18:15:50', 'B', '2025-12-14 06:42:51');
INSERT INTO `chests` VALUES (10, 10, '123', '1', '12', 'FINISHED', 500, 1000, 2, 60, '2025-12-14 18:17:59', '2025-12-14 18:18:03', 'B', '2025-12-14 10:16:20');
INSERT INTO `chests` VALUES (11, 10, '123', '123', '111', 'FINISHED', 1000, 0, 1, 60, '2025-12-14 18:39:21', '2025-12-14 18:39:25', 'B', '2025-12-14 10:18:11');
INSERT INTO `chests` VALUES (12, 10, '123', '123', '12311', 'FINISHED', 500, 0, 1, 300, '2025-12-14 18:44:05', '2025-12-14 18:44:11', 'A', '2025-12-14 10:39:32');
INSERT INTO `chests` VALUES (13, 10, '123', '1', '12', 'FINISHED', 0, 500, 1, 300, '2025-12-14 19:02:28', '2025-12-14 19:02:32', 'B', '2025-12-14 10:44:38');
INSERT INTO `chests` VALUES (14, 10, '123', '11', '22', 'FINISHED', 10911, 9865, 2, 300, '2025-12-14 19:04:26', '2025-12-14 19:04:31', 'A', '2025-12-14 11:03:12');
INSERT INTO `chests` VALUES (15, 10, '123', '11', '12', 'FINISHED', 0, 0, 0, 300, '2025-12-14 19:14:02', '2025-12-14 19:14:06', 'B', '2025-12-14 11:09:53');
INSERT INTO `chests` VALUES (16, 10, '123', '123', '111', 'FINISHED', 0, 0, 0, 3600, '2025-12-14 19:22:41', '2025-12-14 19:22:44', 'B', '2025-12-14 11:14:21');
INSERT INTO `chests` VALUES (17, 10, '123', '11', '22', 'FINISHED', 100, 0, 1, 300, '2025-12-14 19:31:50', '2025-12-14 19:31:54', 'B', '2025-12-14 11:23:14');
INSERT INTO `chests` VALUES (18, 10, '请问', '11', '22', 'FINISHED', 0, 0, 0, 3000, '2025-12-14 19:56:18', '2025-12-14 19:56:22', 'A', '2025-12-14 11:32:03');
INSERT INTO `chests` VALUES (19, 10, '123', '12', '1211', 'FINISHED', 0, 0, 0, 300, '2025-12-14 19:59:04', '2025-12-14 19:59:08', 'REFUND', '2025-12-14 11:56:27');
INSERT INTO `chests` VALUES (20, 12, '测试宝箱 19:57:28', '选项A', '选项B', 'BETTING', 0, 0, 0, 60, NULL, NULL, NULL, '2025-12-14 11:57:30');
INSERT INTO `chests` VALUES (21, 10, '1123', '11', '22', 'FINISHED', 0, 0, 0, 300, '2025-12-15 12:28:43', '2025-12-15 12:28:54', 'REFUND', '2025-12-15 04:28:25');
-- ----------------------------
-- Table structure for streamer_profiles
-- ----------------------------
DROP TABLE IF EXISTS `streamer_profiles`;
CREATE TABLE `streamer_profiles` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL COMMENT '用户ID',
`display_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主播展示名称',
`avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主播头像',
`bio` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '主播简介',
`commission_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '主播抽成比例(%)',
`max_active_chests` int NULL DEFAULT NULL COMMENT '最大活跃宝箱数',
`total_chests` int NULL DEFAULT NULL COMMENT '历史宝箱总数',
`total_winnings` decimal(15, 2) NULL DEFAULT NULL COMMENT '历史获奖总额(分)',
`status` enum('ACTIVE','SUSPENDED','BANNED') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主播状态',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
`updated_at` datetime NULL DEFAULT (now()) COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `user_id`(`user_id` ASC) USING BTREE,
INDEX `ix_streamer_profiles_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of streamer_profiles
-- ----------------------------
INSERT INTO `streamer_profiles` VALUES (1, 9, 'test1', NULL, NULL, 5.00, 10, 0, 0.00, 'ACTIVE', '2025-12-14 03:30:38', '2025-12-14 03:30:38');
INSERT INTO `streamer_profiles` VALUES (2, 7, 'zhubo', NULL, NULL, 5.00, 10, 0, 0.00, 'ACTIVE', '2025-12-14 04:32:10', '2025-12-14 04:32:10');
INSERT INTO `streamer_profiles` VALUES (3, 10, 'zhubo12', NULL, NULL, 5.00, 10, 0, 0.00, 'ACTIVE', '2025-12-14 05:13:20', '2025-12-14 05:13:20');
-- ----------------------------
-- Table structure for system_configs
-- ----------------------------
DROP TABLE IF EXISTS `system_configs`;
CREATE TABLE `system_configs` (
`id` int NOT NULL AUTO_INCREMENT,
`config_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置键',
`config_value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '配置值',
`config_type` enum('STRING','NUMBER','BOOLEAN','JSON') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '配置类型',
`category` enum('GAME_ECONOMY','GAME_LOGIC','SYSTEM_OPERATIONS','UI_DISPLAY') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '配置分类',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '配置描述',
`is_editable` tinyint(1) NULL DEFAULT NULL COMMENT '是否可编辑',
`is_public` tinyint(1) NULL DEFAULT NULL COMMENT '是否对前端公开',
`display_order` int NULL DEFAULT NULL COMMENT '显示顺序',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
`updated_at` datetime NULL DEFAULT (now()) COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_system_configs_config_key`(`config_key` ASC) USING BTREE,
INDEX `ix_system_configs_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 23 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of system_configs
-- ----------------------------
INSERT INTO `system_configs` VALUES (1, 'GAME_NEW_USER_REWARD', '1', 'NUMBER', 'GAME_ECONOMY', '新用户注册奖励(分)', 1, 0, 1, '2025-12-14 02:44:50', '2025-12-14 05:32:52');
INSERT INTO `system_configs` VALUES (2, 'GAME_DAILY_ALLOWANCE', '50', 'NUMBER', 'GAME_ECONOMY', '每日低保(分)', 1, 0, 2, '2025-12-14 02:44:50', '2025-12-14 04:12:58');
INSERT INTO `system_configs` VALUES (3, 'GAME_ALLOWANCE_THRESHOLD', '10', 'NUMBER', 'GAME_ECONOMY', '低保门槛(分)', 1, 0, 3, '2025-12-14 02:44:50', '2025-12-14 05:33:00');
INSERT INTO `system_configs` VALUES (4, 'GAME_HOUSE_EDGE', '0.10', 'NUMBER', 'GAME_ECONOMY', '总抽水比例', 1, 0, 4, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (5, 'GAME_STREAMER_SHARE', '0.05', 'NUMBER', 'GAME_ECONOMY', '主播分润比例', 1, 0, 5, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (6, 'GAME_PLATFORM_SHARE', '0.05', 'NUMBER', 'GAME_ECONOMY', '平台分润比例', 1, 0, 6, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (7, 'GAME_DAILY_CHECKIN_REWARD', '10', 'NUMBER', 'GAME_ECONOMY', '每日签到奖励(分)', 1, 0, 7, '2025-12-14 02:44:50', '2025-12-14 05:33:07');
INSERT INTO `system_configs` VALUES (8, 'GAME_DEFAULT_COUNTDOWN', '300', 'NUMBER', 'GAME_LOGIC', '默认倒计时(秒)', 1, 0, 1, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (9, 'GAME_MIN_COUNTDOWN', '10', 'NUMBER', 'GAME_LOGIC', '最小倒计时(秒)', 1, 0, 2, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (10, 'GAME_MAX_COUNTDOWN', '3600', 'NUMBER', 'GAME_LOGIC', '最大倒计时(秒)', 1, 0, 3, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (11, 'GAME_DEFAULT_MAX_ACTIVE_CHESTS', '10', 'NUMBER', 'GAME_LOGIC', '默认最大活跃宝箱数', 1, 0, 4, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (12, 'GAME_MIN_COMMISSION_RATE', '0.0', 'NUMBER', 'GAME_LOGIC', '最小主播抽成(%)', 1, 0, 5, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (13, 'GAME_MAX_COMMISSION_RATE', '100.0', 'NUMBER', 'GAME_LOGIC', '最大主播抽成(%)', 1, 0, 6, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (14, 'GAME_DEFAULT_COMMISSION_RATE', '5.0', 'NUMBER', 'GAME_LOGIC', '默认主播抽成(%)', 1, 0, 7, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (15, 'PAGINATION_DEFAULT_PAGE_SIZE', '10', 'NUMBER', 'SYSTEM_OPERATIONS', '默认分页大小', 1, 0, 1, '2025-12-14 02:44:50', '2025-12-14 05:33:24');
INSERT INTO `system_configs` VALUES (16, 'PAGINATION_ADMIN_PAGE_SIZE', '10', 'NUMBER', 'SYSTEM_OPERATIONS', '管理员页面分页大小', 1, 0, 2, '2025-12-14 02:44:50', '2025-12-14 05:33:28');
INSERT INTO `system_configs` VALUES (17, 'PAGINATION_ANNOUNCEMENT_PAGE_SIZE', '10', 'NUMBER', 'SYSTEM_OPERATIONS', '公告页面分页大小', 1, 0, 3, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (18, 'WS_BROADCAST_INTERVAL', '200', 'NUMBER', 'SYSTEM_OPERATIONS', 'WebSocket广播间隔(毫秒)', 1, 0, 4, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (19, 'WS_HEARTBEAT_INTERVAL', '30', 'NUMBER', 'SYSTEM_OPERATIONS', 'WebSocket心跳间隔(秒)', 1, 0, 5, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (20, 'WS_CONNECTION_TIMEOUT', '60', 'NUMBER', 'SYSTEM_OPERATIONS', 'WebSocket连接超时(秒)', 1, 0, 6, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (21, 'ANNOUNCEMENT_DEFAULT_PRIORITY', '0', 'NUMBER', 'UI_DISPLAY', '公告默认优先级', 1, 0, 1, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
INSERT INTO `system_configs` VALUES (22, 'ANNOUNCEMENT_MAX_PRIORITY', '100', 'NUMBER', 'UI_DISPLAY', '公告最大优先级', 1, 0, 2, '2025-12-14 02:44:50', '2025-12-14 02:44:50');
-- ----------------------------
-- Table structure for transactions
-- ----------------------------
DROP TABLE IF EXISTS `transactions`;
CREATE TABLE `transactions` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL COMMENT '用户ID',
`type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '交易类型',
`amount` bigint NOT NULL COMMENT '金额(分)',
`balance_after` bigint NOT NULL COMMENT '交易后余额',
`related_id` int NULL DEFAULT NULL COMMENT '关联ID',
`description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '描述',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `ix_transactions_id`(`id` ASC) USING BTREE,
INDEX `ix_transactions_user_id`(`user_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 64 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of transactions
-- ----------------------------
INSERT INTO `transactions` VALUES (1, 5, '低保', 5000, 5000, NULL, '每日低保', '2025-12-13 07:14:26');
INSERT INTO `transactions` VALUES (2, 6, '注册奖励', 100000, 0, NULL, '新用户注册奖励', '2025-12-13 07:39:19');
INSERT INTO `transactions` VALUES (3, 6, '低保', 5000, 5000, NULL, '每日低保', '2025-12-13 07:39:23');
INSERT INTO `transactions` VALUES (4, 7, '注册奖励', 100000, 0, NULL, '新用户注册奖励', '2025-12-13 07:46:22');
INSERT INTO `transactions` VALUES (5, 7, '低保', 5000, 5000, NULL, '每日低保', '2025-12-13 07:46:38');
INSERT INTO `transactions` VALUES (6, 8, '注册奖励', 100000, 0, NULL, '新用户注册奖励', '2025-12-13 09:03:37');
INSERT INTO `transactions` VALUES (7, 8, '低保', 5000, 5000, NULL, '每日低保', '2025-12-13 09:07:49');
INSERT INTO `transactions` VALUES (8, 9, '注册奖励', 100000, 0, NULL, '新用户注册奖励', '2025-12-13 09:22:26');
INSERT INTO `transactions` VALUES (9, 9, '低保', 5000, 5000, NULL, '每日低保', '2025-12-13 09:28:01');
INSERT INTO `transactions` VALUES (10, 5, '管理员调整', -22211, -17211, NULL, '管理员调整 (操作人: admin)', '2025-12-14 02:42:50');
INSERT INTO `transactions` VALUES (11, 9, '管理员调整', 22, 5022, NULL, '管理员调整 (操作人: admin)', '2025-12-14 03:24:27');
INSERT INTO `transactions` VALUES (12, 7, '下注', -500, 4500, 3, '下注sdaf(A边)', '2025-12-14 04:35:46');
INSERT INTO `transactions` VALUES (13, 9, '下注', -1000, 4022, 3, '下注sdaf(A边)', '2025-12-14 04:39:06');
INSERT INTO `transactions` VALUES (14, 9, '下注', -1000, 3022, 3, '下注sdaf(B边)', '2025-12-14 04:44:04');
INSERT INTO `transactions` VALUES (15, 9, '下注', -1000, 2022, 3, '追加下注sdaf(B边)', '2025-12-14 04:44:10');
INSERT INTO `transactions` VALUES (16, 9, '下注', -500, 1522, 3, '追加下注sdaf(B边)', '2025-12-14 04:44:18');
INSERT INTO `transactions` VALUES (17, 9, '下注', -1000, 522, 3, '追加下注sdaf(A边)', '2025-12-14 04:46:23');
INSERT INTO `transactions` VALUES (18, 9, '下注', -100, 422, 3, '追加下注sdaf(A边)', '2025-12-14 04:46:28');
INSERT INTO `transactions` VALUES (19, 10, '注册奖励', 100000, 0, NULL, '新用户注册奖励', '2025-12-14 05:12:53');
INSERT INTO `transactions` VALUES (20, 9, '下注', -100, 322, 4, '下注ad(A边)', '2025-12-14 05:15:13');
INSERT INTO `transactions` VALUES (21, 9, '下注', -100, 222, 4, '追加下注ad(A边)', '2025-12-14 05:15:19');
INSERT INTO `transactions` VALUES (22, 5, '低保', 5000, -12211, NULL, '每日低保', '2025-12-14 05:17:33');
INSERT INTO `transactions` VALUES (23, 9, '管理员调整', 111111, 111333, NULL, '管理员调整 (操作人: admin)', '2025-12-14 05:18:01');
INSERT INTO `transactions` VALUES (24, 5, '管理员调整', 111111, 98900, NULL, '省分公司个 (操作人: admin)', '2025-12-14 05:18:06');
INSERT INTO `transactions` VALUES (25, 5, '管理员调整', 111, 99011, NULL, '管理员调整 (操作人: admin)', '2025-12-14 05:18:25');
INSERT INTO `transactions` VALUES (26, 5, '管理员调整', 3452342, 3551353, NULL, '管理员调整 (操作人: admin)', '2025-12-14 05:18:48');
INSERT INTO `transactions` VALUES (27, 9, '下注', -500, 110833, 6, '下注123(A边)', '2025-12-14 05:23:54');
INSERT INTO `transactions` VALUES (28, 9, '下注', -1000, 109833, 6, '下注123(B边)', '2025-12-14 05:23:58');
INSERT INTO `transactions` VALUES (29, 9, '下注', -500, 109333, 6, '追加下注123(A边)', '2025-12-14 05:24:04');
INSERT INTO `transactions` VALUES (30, 9, '下注', -1000, 108333, 6, '追加下注123(A边)', '2025-12-14 05:24:13');
INSERT INTO `transactions` VALUES (31, 9, '下注', -1000, 107333, 6, '追加下注123(A边)', '2025-12-14 05:24:27');
INSERT INTO `transactions` VALUES (32, 10, '低保', 5000, 5000, NULL, '每日低保', '2025-12-14 05:25:12');
INSERT INTO `transactions` VALUES (33, 10, '下注', -100, 4900, 3, '下注sdaf(A边)', '2025-12-14 05:26:08');
INSERT INTO `transactions` VALUES (34, 10, '抽水分润', 200, 5100, 6, '宝箱《123》抽水分润', '2025-12-14 05:36:42');
INSERT INTO `transactions` VALUES (35, 9, '获胜', 3600, 110933, 6, '宝箱《123》获胜奖金', '2025-12-14 05:36:42');
INSERT INTO `transactions` VALUES (36, 10, '抽水分润', 10, 5110, 4, '宝箱《ad》抽水分润', '2025-12-14 05:36:59');
INSERT INTO `transactions` VALUES (37, 9, '获胜', 180, 111113, 4, '宝箱《ad》获胜奖金', '2025-12-14 05:36:59');
INSERT INTO `transactions` VALUES (38, 7, '管理员调整', 111, 4611, NULL, '管理员调整 (操作人: admin)', '2025-12-14 05:59:43');
INSERT INTO `transactions` VALUES (39, 5, '管理员调整', -10000, 3541353, NULL, '管理员调整 (操作人: admin)', '2025-12-14 05:59:53');
INSERT INTO `transactions` VALUES (40, 5, '管理员调整', -3540000, 1353, NULL, '管理员调整 (操作人: admin)', '2025-12-14 06:00:13');
INSERT INTO `transactions` VALUES (41, 9, '下注', -100, 111013, 3, '追加下注sdaf(A边)', '2025-12-14 06:11:27');
INSERT INTO `transactions` VALUES (42, 9, '下注', -100, 110913, 3, '追加下注sdaf(A边)', '2025-12-14 06:11:33');
INSERT INTO `transactions` VALUES (43, 5, '管理员调整', 1111, 2464, NULL, '管理员调整 (操作人: admin)', '2025-12-14 06:12:33');
INSERT INTO `transactions` VALUES (44, 9, '下注', -100, 110813, 9, '下注123(A边)', '2025-12-14 06:43:42');
INSERT INTO `transactions` VALUES (45, 10, '抽水分润', 5, 5115, 9, '宝箱《123》抽水分润', '2025-12-14 10:15:49');
INSERT INTO `transactions` VALUES (46, 10, '下注', -1000, 4115, 3, '追加下注sdaf(A边)', '2025-12-14 10:16:07');
INSERT INTO `transactions` VALUES (47, 9, '下注', -500, 110313, 10, '下注123(A边)', '2025-12-14 10:16:39');
INSERT INTO `transactions` VALUES (48, 9, '下注', -1000, 109313, 10, '下注123(B边)', '2025-12-14 10:17:11');
INSERT INTO `transactions` VALUES (49, 10, '抽水分润', 75, 4190, 10, '宝箱《123》抽水分润', '2025-12-14 10:18:02');
INSERT INTO `transactions` VALUES (50, 9, '获胜', 1350, 110663, 10, '宝箱《123》获胜奖金', '2025-12-14 10:18:02');
INSERT INTO `transactions` VALUES (51, 9, '下注', -1000, 109663, 11, '下注123(A边)', '2025-12-14 10:19:24');
INSERT INTO `transactions` VALUES (52, 10, '抽水分润', 50, 4240, 11, '宝箱《123》抽水分润', '2025-12-14 10:39:25');
INSERT INTO `transactions` VALUES (53, 9, '下注', -500, 109163, 12, '下注123(A边)', '2025-12-14 10:39:53');
INSERT INTO `transactions` VALUES (54, 10, '抽水分润', 25, 4265, 12, '宝箱《123》抽水分润', '2025-12-14 10:44:08');
INSERT INTO `transactions` VALUES (55, 9, '下注', -500, 109113, 13, '下注123(B边)', '2025-12-14 10:56:23');
INSERT INTO `transactions` VALUES (56, 10, '抽水分润', 25, 4290, 13, '宝箱《123》抽水分润', '2025-12-14 11:02:31');
INSERT INTO `transactions` VALUES (57, 9, '获胜', 450, 109563, 13, '宝箱《123》获胜奖金', '2025-12-14 11:02:31');
INSERT INTO `transactions` VALUES (58, 9, '下注', -10911, 98652, 14, '下注123(A边)', '2025-12-14 11:03:29');
INSERT INTO `transactions` VALUES (59, 9, '下注', -9865, 88787, 14, '下注123(B边)', '2025-12-14 11:03:38');
INSERT INTO `transactions` VALUES (60, 10, '抽水分润', 1038, 5328, 14, '宝箱《123》抽水分润', '2025-12-14 11:04:30');
INSERT INTO `transactions` VALUES (61, 9, '获胜', 18700, 107487, 14, '宝箱《123》获胜奖金', '2025-12-14 11:04:30');
INSERT INTO `transactions` VALUES (62, 9, '下注', -100, 107387, 17, '下注123(A边)', '2025-12-14 11:23:26');
INSERT INTO `transactions` VALUES (63, 10, '抽水分润', 5, 5333, 17, '宝箱《123》抽水分润', '2025-12-14 11:31:54');
-- ----------------------------
-- Table structure for user_operation_logs
-- ----------------------------
DROP TABLE IF EXISTS `user_operation_logs`;
CREATE TABLE `user_operation_logs` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL COMMENT '操作用户ID',
`operator_id` int NULL DEFAULT NULL COMMENT '操作人ID管理员',
`operation_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作类型',
`operation_details` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL COMMENT '操作详情(JSON格式)',
`ip_address` varchar(45) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'IP地址',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `ix_user_operation_logs_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user_operation_logs
-- ----------------------------
-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱',
`hashed_password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '哈希密码',
`role` enum('USER','STREAMER','ADMIN') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户角色',
`nickname` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户昵称',
`avatar_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像URL',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`status` enum('ACTIVE','DISABLED','BANNED') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '用户状态',
`balance` bigint NULL DEFAULT NULL COMMENT '余额(分)',
`version` int NULL DEFAULT NULL COMMENT '乐观锁版本号',
`last_login_at` datetime NULL DEFAULT NULL COMMENT '最后登录时间',
`login_count` int NULL DEFAULT NULL COMMENT '登录次数',
`created_at` datetime NULL DEFAULT (now()) COMMENT '创建时间',
`updated_at` datetime NULL DEFAULT (now()) COMMENT '更新时间',
`is_active` tinyint NULL DEFAULT NULL COMMENT '是否激活(1:是 0:否)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `ix_users_email`(`email` ASC) USING BTREE,
UNIQUE INDEX `ix_users_username`(`username` ASC) USING BTREE,
INDEX `ix_users_id`(`id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'testuser', 'test@example.com', '$scrypt$ln=16,r=8,p=1$sdYa4zzHuHduLQWAsLZWSg$jUu5IbiK+htBbLOt/qBP6WR5zQRMMPwbhVDx6aP/dKw', 'USER', NULL, NULL, NULL, 'ACTIVE', 0, 0, NULL, 0, '2025-12-13 03:07:55', '2025-12-14 02:03:21', 1);
INSERT INTO `users` VALUES (2, 'testuser123', 'test123@example.com', '$scrypt$ln=16,r=8,p=1$Zux9b21NiTFGqNXamzOG0A$4gQGVyGfobpB87bjk44+dwhX5b/tj7x8YTxGT83aFHg', 'USER', NULL, NULL, NULL, 'ACTIVE', 0, 0, NULL, 0, '2025-12-13 03:07:55', '2025-12-13 03:07:55', 1);
INSERT INTO `users` VALUES (3, 'test', 'test@test.com', '$scrypt$ln=16,r=8,p=1$VMoZ41zL2ZsTYoxx7v0/Bw$yQOZIticgjRM7s4MTVXV44wgYgehsrxAoki27ot2fAc', 'USER', NULL, NULL, NULL, 'ACTIVE', 0, 0, NULL, 0, '2025-12-13 03:07:55', '2025-12-13 03:07:55', 1);
INSERT INTO `users` VALUES (5, 'admin', 'amdin@ad.c', '$scrypt$ln=16,r=8,p=1$AaDU+l9rjTGGsLYWIsS4tw$EyZmBBLjJifJSiBWexovADnvvR2hRq8NMSZIBePBzfs', 'ADMIN', NULL, NULL, NULL, 'ACTIVE', 2464, 7, NULL, 0, '2025-12-13 03:10:35', '2025-12-14 06:12:33', 1);
INSERT INTO `users` VALUES (6, 'user1', 'user@qq.com', '$scrypt$ln=16,r=8,p=1$xzhHiHGu1dq7lzIGQAiBMA$/IMDnslsTmQSNclXcX1z6z8/6RX9mUdgDIJU9PZaOV4', 'USER', NULL, NULL, NULL, 'ACTIVE', 5000, 0, NULL, 0, '2025-12-13 07:39:19', '2025-12-13 07:39:23', 1);
INSERT INTO `users` VALUES (7, 'zhubo', 'zhobo@zhuobo.com', '$scrypt$ln=16,r=8,p=1$dk5pzbnXOgfAmJOylrK21g$QhgDI2BJdlRDSkAIc6TMsuU10IP97arhwraxw0MKZWk', 'STREAMER', NULL, NULL, NULL, 'ACTIVE', 4611, 2, NULL, 0, '2025-12-13 07:46:22', '2025-12-14 05:59:43', 1);
INSERT INTO `users` VALUES (8, 'zhubo1', 'zhuobo12@q.q', '$scrypt$ln=16,r=8,p=1$uxeCcK5VqrV2DuEcQwiBsA$OwN51jBQIbyGhCnJfWTeWcOQy3FIa3qnljkCuokWMjo', 'USER', NULL, NULL, NULL, 'ACTIVE', 5000, 0, NULL, 0, '2025-12-13 09:03:37', '2025-12-14 02:03:48', 1);
INSERT INTO `users` VALUES (9, 'test1', 'te11st@test.com', '$scrypt$ln=16,r=8,p=1$RYgRIiRkTIlxLmWMkfKekw$e0bQSZ8aoelJq0X3A+x8xRXHVDiDGZ8Y+evhnNs48mM', 'USER', NULL, NULL, NULL, 'ACTIVE', 107387, 26, NULL, 0, '2025-12-13 09:22:26', '2025-12-14 11:23:25', 1);
INSERT INTO `users` VALUES (10, 'zhubo12', 'zhu@q.oc', '$scrypt$ln=16,r=8,p=1$x3hPyflfKwXAOAfA2Fvr/Q$mstdmf0PWLrdrbrWGYDXZHdO79CdTmJqPg7/Dt38vLg', 'STREAMER', NULL, NULL, NULL, 'ACTIVE', 5333, 2, NULL, 0, '2025-12-14 05:12:53', '2025-12-14 11:31:54', 1);
INSERT INTO `users` VALUES (11, 'streamer1', 'streamer1@example.com', '$scrypt$ln=16,r=8,p=1$KkVo7T0nZAyhNIYQIqR0Lg$iqN2qdCYPzgfhLiLDhgCAuTKzF0Nr1+1alm3/zVL0IU', 'STREAMER', NULL, NULL, NULL, 'ACTIVE', 0, 0, NULL, 0, '2025-12-14 11:27:41', '2025-12-14 11:27:41', 1);
INSERT INTO `users` VALUES (12, 'streamer', 'streamer@example.com', '$scrypt$ln=16,r=8,p=1$BWAsRYixVqqV8t7b27v3ng$8O+rQCKRJtTE56I2oYyRR8Zjl6tu/JPa4XgiFDdKayY', 'STREAMER', '测试主播', NULL, NULL, 'ACTIVE', 500000, 0, NULL, 0, '2025-12-14 11:57:11', '2025-12-14 11:57:11', 1);
INSERT INTO `users` VALUES (13, 'user', 'user@example.com', '$scrypt$ln=16,r=8,p=1$UGpNqZWy1ppzjjEmROi9tw$d9aGNBDqO0fyGnSttv1NpMs7Ra0uovlAsT/cF5FDxlU', 'USER', '测试用户', NULL, NULL, 'ACTIVE', 200000, 0, NULL, 0, '2025-12-14 11:57:11', '2025-12-14 11:57:11', 1);
SET FOREIGN_KEY_CHECKS = 1;

View File

@ -5,11 +5,11 @@ VITE_APP_TITLE=宝箱竞猜系统
VITE_FRONTEND_PORT=3000
# 后端配置
VITE_BACKEND_URL=http://localhost:8000
VITE_BACKEND_WS_URL=ws://localhost:8000
VITE_BACKEND_URL=http://127.0.0.1:8000
VITE_BACKEND_WS_URL=ws://127.0.0.1:8000
# API配置
VITE_API_BASE_URL=http://localhost:8000
VITE_API_BASE_URL=http://127.0.0.1:8000
# WebSocket配置
VITE_WEBSOCKET_BASE_URL=ws://localhost:8000
VITE_WEBSOCKET_BASE_URL=ws://127.0.0.1:8000

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>互动竞猜开宝箱系统</title>
<script type="module" crossorigin src="/assets/index-DUKe-Ir9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B4_1kpoc.css">
<script type="module" crossorigin src="/assets/index-ByW5lIiv.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D9Z2CB14.css">
</head>
<body>
<div id="root"></div>

View File

@ -1,14 +1,13 @@
import { useState, useEffect } from 'react';
import { gameApi, announcementApi, streamerApi } from '../services/api';
import { gameApi, announcementApi } from '../services/api';
import { websocketService } from '../services/websocket';
import ChestCard from '../components/ChestCard';
import Loading from '../components/Loading';
import type { Chest, Announcement, User, CountdownUpdateMessage, ChestStatusMessage } from '../types';
import type { Chest, Announcement, CountdownUpdateMessage, ChestStatusMessage } from '../types';
const HomePage = () => {
const [chests, setChests] = useState<Chest[]>([]);
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [activeStreamers, setActiveStreamers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [currentAnnouncementIndex, setCurrentAnnouncementIndex] = useState(0);
@ -79,10 +78,9 @@ const HomePage = () => {
const loadData = async () => {
try {
setLoading(true);
const [chestsData, announcementsData, streamersData] = await Promise.all([
const [chestsData, announcementsData] = await Promise.all([
gameApi.getChests(),
announcementApi.getActiveAnnouncements(5).catch(() => []),
streamerApi.getStreamers(0, 10).catch(() => []),
]);
// 调试:打印宝箱数据
@ -96,7 +94,6 @@ const HomePage = () => {
console.log('[HomePage] Active chests:', activeChests);
setChests(activeChests);
setAnnouncements(announcementsData);
setActiveStreamers(streamersData);
} catch (err: any) {
setError('加载数据失败');
console.error(err);
@ -161,32 +158,6 @@ const HomePage = () => {
<div className="alert alert-danger">{error}</div>
)}
{/* 正在开奖的主播 */}
{activeStreamers.length > 0 && (
<div className="streamers-section">
<h2 className="section-title">🌟 </h2>
<div className="streamers-grid">
{activeStreamers.map((streamer) => (
<div key={streamer.id} className="streamer-card">
<div className="streamer-info">
<div className="streamer-avatar">
{streamer.nickname?.charAt(0).toUpperCase() || streamer.username.charAt(0).toUpperCase()}
</div>
<div className="streamer-details">
<h4>{streamer.nickname || streamer.username}</h4>
<p className="streamer-status"> </p>
</div>
</div>
<div className="streamer-stats">
<span>: {chests.filter(c => c.streamer_id === streamer.id).length}</span>
<span>: ¥{streamer.balance.toLocaleString()}</span>
</div>
</div>
))}
</div>
</div>
)}
{/* 活跃宝箱列表 */}
<div className="streamers-section">
<h2 className="section-title">🎁 </h2>

View File

@ -1,4 +1,3 @@
import { io, Socket } from 'socket.io-client';
import type { WebSocketMessage } from '../types';
const API_BASE_URL = import.meta.env.VITE_WEBSOCKET_BASE_URL || 'ws://localhost:8000';
@ -6,7 +5,7 @@ const API_BASE_URL = import.meta.env.VITE_WEBSOCKET_BASE_URL || 'ws://localhost:
type MessageHandler = (message: WebSocketMessage) => void;
class WebSocketService {
private ws: Socket | null = null;
private ws: WebSocket | null = null;
private listeners: Array<(message: any) => void> = [];
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
@ -17,7 +16,7 @@ class WebSocketService {
private token: string | null = null;
connect(role: 'user' | 'streamer', id: number, token: string) {
console.log(`Connecting Socket.IO for ${role} ${id} with token: ${token.substring(0, 20)}...`);
console.log(`Connecting WebSocket for ${role} ${id} with token: ${token.substring(0, 20)}...`);
// 保存连接参数
this.role = role;
@ -37,58 +36,49 @@ class WebSocketService {
return;
}
// 构建Socket.IO连接URL - 移除socket.io路径让客户端自动处理
const url = API_BASE_URL;
console.log(`Socket.IO connection URL: ${url}`);
// 构建原生 WebSocket 连接 URL
// 后端路由: /ws/streamer/{streamer_id} 和 /ws/user/{user_id}
const wsUrl = `${API_BASE_URL}/ws/${role}/${id}?token=${encodeURIComponent(token)}`;
console.log(`WebSocket connection URL: ${wsUrl}`);
// 创建Socket.IO连接
this.ws = io(url, {
transports: ['websocket'],
query: {
role,
id: id.toString(),
token
},
reconnection: false, // 禁用自动重连,使用手动重连
timeout: 10000, // 10秒超时开发环境
forceNew: true // 强制新连接
});
// 创建原生 WebSocket 连接
this.ws = new WebSocket(wsUrl);
// 监听连接事件 - 使用Socket.IO事件
this.ws.on('connect', () => {
console.log('Socket.IO connected successfully');
// 监听连接事件
this.ws.onopen = () => {
console.log('WebSocket connected successfully');
this.reconnectAttempts = 0;
// 启动心跳
this.startHeartbeat();
});
};
this.ws.on('connect_error', (error) => {
console.error('Socket.IO connection error:', error);
this.handleReconnect();
});
this.ws.onerror = (error) => {
console.error('WebSocket connection error:', error);
};
this.ws.on('disconnect', (reason) => {
console.log('Socket.IO disconnected:', reason);
this.ws.onclose = (event) => {
console.log('WebSocket disconnected:', event.code, event.reason);
// 停止心跳
this.stopHeartbeat();
// 自动重连(除非是客户端主动断开
if (reason !== 'io client disconnect' && this.reconnectAttempts < this.maxReconnectAttempts) {
// 自动重连(除非是正常关闭
if (event.code !== 1000 && this.reconnectAttempts < this.maxReconnectAttempts) {
this.handleReconnect();
}
});
};
// 监听消息事件
this.ws.on('message', (data: any) => {
this.ws.onmessage = (event) => {
try {
console.log('Received Socket.IO message:', data);
const data = JSON.parse(event.data);
console.log('Received WebSocket message:', data);
this.listeners.forEach(listener => listener(data));
} catch (error) {
console.error('Failed to process Socket.IO message:', error);
console.error('Failed to process WebSocket message:', error);
}
});
};
}
private handleReconnect() {
@ -115,9 +105,9 @@ class WebSocketService {
private startHeartbeat() {
this.stopHeartbeat();
this.heartbeatTimer = window.setInterval(() => {
if (this.ws?.connected) {
// 发送心跳(可选)
// this.ws.emit('ping');
if (this.ws?.readyState === WebSocket.OPEN) {
// 发送心跳 ping
this.ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000); // 30秒心跳
}
@ -139,7 +129,7 @@ class WebSocketService {
// 关闭连接
if (this.ws) {
this.ws.disconnect();
this.ws.close(1000, 'Client disconnect');
this.ws = null;
}
@ -157,17 +147,17 @@ class WebSocketService {
}
send(data: any) {
if (this.ws?.connected) {
this.ws.emit('message', data);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
} else {
console.warn('Socket.IO is not connected');
console.warn('WebSocket is not connected');
}
}
isConnected(): boolean {
return this.ws?.connected || false;
return this.ws?.readyState === WebSocket.OPEN;
}
}
export const websocketService = new WebSocketService();
export default WebSocketService;
export default WebSocketService;

View File

@ -12,8 +12,8 @@
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],

View File

@ -5,6 +5,7 @@ import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 3000,
proxy: {
'/api': {

View File

@ -1,199 +0,0 @@
-- ============================================
-- 宝箱游戏系统 MySQL 初始化脚本
-- 生成时间: 2025-12-13
-- 字符集: utf8mb4
-- ============================================
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS treasure_box_game
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE treasure_box_game;
-- ============================================
-- 1. 用户表 (users)
-- ============================================
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
hashed_password VARCHAR(255) NOT NULL COMMENT '哈希密码',
role ENUM('USER', 'STREAMER', 'ADMIN') NOT NULL DEFAULT 'USER' COMMENT '用户角色',
nickname VARCHAR(64) DEFAULT NULL COMMENT '用户昵称',
avatar_url VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
phone VARCHAR(20) DEFAULT NULL COMMENT '手机号',
status ENUM('ACTIVE', 'DISABLED', 'BANNED') NOT NULL DEFAULT 'ACTIVE' COMMENT '用户状态',
balance BIGINT NOT NULL DEFAULT 0 COMMENT '余额(单位:分)',
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
last_login_at DATETIME DEFAULT NULL COMMENT '最后登录时间',
login_count INT NOT NULL DEFAULT 0 COMMENT '登录次数',
is_active TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否激活',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_username (username),
INDEX idx_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- ============================================
-- 2. 交易记录表 (transactions)
-- ============================================
CREATE TABLE IF NOT EXISTS transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
type VARCHAR(30) NOT NULL COMMENT '交易类型',
amount BIGINT NOT NULL COMMENT '金额(单位:分)',
balance_after BIGINT NOT NULL COMMENT '交易后余额',
related_id INT DEFAULT NULL COMMENT '关联ID',
description TEXT DEFAULT NULL COMMENT '描述',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='交易记录表';
-- ============================================
-- 3. 宝箱表 (chests)
-- ============================================
CREATE TABLE IF NOT EXISTS chests (
id INT AUTO_INCREMENT PRIMARY KEY,
streamer_id INT NOT NULL COMMENT '主播ID',
title VARCHAR(200) NOT NULL COMMENT '宝箱标题',
option_a VARCHAR(100) NOT NULL COMMENT '选项A',
option_b VARCHAR(100) NOT NULL COMMENT '选项B',
status ENUM('BETTING', 'LOCKED', 'SETTLING', 'FINISHED', 'REFUNDED') NOT NULL DEFAULT 'BETTING' COMMENT '宝箱状态',
pool_a BIGINT NOT NULL DEFAULT 0 COMMENT 'A边奖池(单位:分)',
pool_b BIGINT NOT NULL DEFAULT 0 COMMENT 'B边奖池(单位:分)',
total_bets INT NOT NULL DEFAULT 0 COMMENT '总下注次数',
countdown_seconds INT DEFAULT NULL COMMENT '倒计时(秒)',
locked_at DATETIME DEFAULT NULL COMMENT '封盘时间',
settled_at DATETIME DEFAULT NULL COMMENT '结算时间',
result VARCHAR(10) DEFAULT NULL COMMENT '结算结果',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_streamer_id (streamer_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='宝箱表';
-- ============================================
-- 4. 下注记录表 (bets)
-- ============================================
CREATE TABLE IF NOT EXISTS bets (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '用户ID',
chest_id INT NOT NULL COMMENT '宝箱ID',
`option` CHAR(1) NOT NULL COMMENT '下注选项(A或B)',
amount BIGINT NOT NULL COMMENT '下注金额(单位:分)',
payout BIGINT DEFAULT NULL COMMENT '获奖金额(单位:分)',
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '状态',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id),
INDEX idx_chest_id (chest_id),
CONSTRAINT fk_bets_chest FOREIGN KEY (chest_id) REFERENCES chests(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='下注记录表';
-- ============================================
-- 5. 系统配置表 (system_configs)
-- ============================================
CREATE TABLE IF NOT EXISTS system_configs (
id INT AUTO_INCREMENT PRIMARY KEY,
config_key VARCHAR(64) NOT NULL UNIQUE COMMENT '配置键',
config_value TEXT DEFAULT NULL COMMENT '配置值',
config_type ENUM('STRING', 'NUMBER', 'BOOLEAN', 'JSON') NOT NULL DEFAULT 'STRING' COMMENT '配置类型',
category ENUM('GAME_ECONOMY', 'GAME_LOGIC', 'SYSTEM_OPERATIONS', 'UI_DISPLAY') NOT NULL COMMENT '配置分类',
description VARCHAR(255) DEFAULT NULL COMMENT '配置描述',
is_editable TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否可编辑',
is_public TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否对前端公开',
display_order INT NOT NULL DEFAULT 0 COMMENT '显示顺序',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_config_key (config_key),
INDEX idx_category (category)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表';
-- ============================================
-- 6. 主播资料表 (streamer_profiles)
-- ============================================
CREATE TABLE IF NOT EXISTS streamer_profiles (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL UNIQUE COMMENT '用户ID',
display_name VARCHAR(64) DEFAULT NULL COMMENT '主播展示名称',
avatar_url VARCHAR(255) DEFAULT NULL COMMENT '主播头像',
bio TEXT DEFAULT NULL COMMENT '主播简介',
commission_rate DECIMAL(5,2) NOT NULL DEFAULT 5.00 COMMENT '主播抽成比例(%)',
max_active_chests INT NOT NULL DEFAULT 10 COMMENT '最大活跃宝箱数',
total_chests INT NOT NULL DEFAULT 0 COMMENT '历史宝箱总数',
total_winnings DECIMAL(15,2) NOT NULL DEFAULT 0.00 COMMENT '历史获奖总额',
status ENUM('ACTIVE', 'SUSPENDED', 'BANNED') NOT NULL DEFAULT 'ACTIVE' COMMENT '主播状态',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_user_id (user_id),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='主播资料表';
-- ============================================
-- 7. 用户操作日志表 (user_operation_logs)
-- ============================================
CREATE TABLE IF NOT EXISTS user_operation_logs (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL COMMENT '操作用户ID',
operator_id INT DEFAULT NULL COMMENT '操作人ID(管理员)',
operation_type VARCHAR(64) NOT NULL COMMENT '操作类型',
operation_details TEXT DEFAULT NULL COMMENT '操作详情(JSON格式)',
ip_address VARCHAR(45) DEFAULT NULL COMMENT 'IP地址',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
INDEX idx_user_id (user_id),
INDEX idx_operator_id (operator_id),
INDEX idx_operation_type (operation_type),
INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户操作日志表';
-- ============================================
-- 8. 公告表 (announcements)
-- ============================================
CREATE TABLE IF NOT EXISTS announcements (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL COMMENT '公告标题',
content TEXT NOT NULL COMMENT '公告内容',
type ENUM('INFO', 'WARNING', 'PRIZE') NOT NULL DEFAULT 'INFO' COMMENT '公告类型',
is_pinned TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否置顶',
priority INT NOT NULL DEFAULT 0 COMMENT '优先级(数字越大优先级越高)',
starts_at DATETIME DEFAULT NULL COMMENT '生效时间',
expires_at DATETIME DEFAULT NULL COMMENT '过期时间',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_type (type),
INDEX idx_is_pinned (is_pinned),
INDEX idx_starts_at (starts_at),
INDEX idx_expires_at (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='公告表';
-- ============================================
-- 初始化默认数据
-- ============================================
-- 插入默认管理员账户 (密码: admin123请及时修改)
-- 密码使用 bcrypt 加密,此处为示例哈希值
INSERT INTO users (username, email, hashed_password, role, nickname, status, is_active)
VALUES ('admin', 'admin@example.com', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.qXaFn8c0LsMLQe', 'ADMIN', '系统管理员', 'ACTIVE', 1)
ON DUPLICATE KEY UPDATE username = username;
-- 插入默认系统配置
INSERT INTO system_configs (config_key, config_value, config_type, category, description, is_editable, is_public, display_order) VALUES
-- 游戏经济配置
('min_bet_amount', '100', 'NUMBER', 'GAME_ECONOMY', '最小下注金额(分)', 1, 1, 1),
('max_bet_amount', '100000', 'NUMBER', 'GAME_ECONOMY', '最大下注金额(分)', 1, 1, 2),
('platform_fee_rate', '5', 'NUMBER', 'GAME_ECONOMY', '平台抽成比例(%)', 1, 0, 3),
('default_commission_rate', '5', 'NUMBER', 'GAME_ECONOMY', '默认主播抽成比例(%)', 1, 0, 4),
-- 游戏逻辑配置
('default_countdown_seconds', '60', 'NUMBER', 'GAME_LOGIC', '默认倒计时秒数', 1, 1, 10),
('max_active_chests_per_streamer', '10', 'NUMBER', 'GAME_LOGIC', '每个主播最大活跃宝箱数', 1, 0, 11),
-- 系统运营配置
('maintenance_mode', 'false', 'BOOLEAN', 'SYSTEM_OPERATIONS', '维护模式', 1, 1, 20),
('registration_enabled', 'true', 'BOOLEAN', 'SYSTEM_OPERATIONS', '是否开放注册', 1, 1, 21),
-- UI显示配置
('site_name', '宝箱游戏', 'STRING', 'UI_DISPLAY', '网站名称', 1, 1, 30),
('site_logo_url', '', 'STRING', 'UI_DISPLAY', '网站Logo URL', 1, 1, 31)
ON DUPLICATE KEY UPDATE config_key = config_key;
-- ============================================
-- 完成
-- ============================================
SELECT '数据库初始化完成!' AS message;

6
package-lock.json generated
View File

@ -1,6 +0,0 @@
{
"name": "demo07",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

View File

@ -1,6 +0,0 @@
# 开发依赖
-r requirements.txt
pytest==7.4.3
pytest-asyncio==0.21.1
httpx==0.25.2