第三版
This commit is contained in:
parent
eb11867a98
commit
4927420266
2
.env
2
.env
@ -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
|
||||
|
||||
130
QUICKSTART.md
130
QUICKSTART.md
@ -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 文档了解所有接口
|
||||
|
||||
**祝您使用愉快!** 🎉
|
||||
58
backend/.env
58
backend/.env
@ -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
|
||||
|
||||
@ -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文件中未定义的变量
|
||||
)
|
||||
|
||||
|
||||
@ -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():
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
|
||||
@ -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]:
|
||||
"""
|
||||
|
||||
356
baoxiang.sql
356
baoxiang.sql
@ -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;
|
||||
@ -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
|
||||
1
frontend/dist/assets/index-B4_1kpoc.css
vendored
1
frontend/dist/assets/index-B4_1kpoc.css
vendored
File diff suppressed because one or more lines are too long
94
frontend/dist/assets/index-DUKe-Ir9.js
vendored
94
frontend/dist/assets/index-DUKe-Ir9.js
vendored
File diff suppressed because one or more lines are too long
4
frontend/dist/index.html
vendored
4
frontend/dist/index.html
vendored
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -12,8 +12,8 @@
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
|
||||
@ -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': {
|
||||
|
||||
199
init_mysql.sql
199
init_mysql.sql
@ -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
6
package-lock.json
generated
@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "demo07",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
# 开发依赖
|
||||
-r requirements.txt
|
||||
|
||||
pytest==7.4.3
|
||||
pytest-asyncio==0.21.1
|
||||
httpx==0.25.2
|
||||
Loading…
Reference in New Issue
Block a user