第二词提交哦啊

This commit is contained in:
taiyi 2025-12-17 11:43:50 +08:00
parent 30fcbf46ca
commit 5259325b94
7 changed files with 133 additions and 95 deletions

View File

@ -21,9 +21,9 @@ class ServerSettings(BaseSettings):
class DatabaseSettings(BaseSettings):
"""数据库配置"""
# MySQL连接格式: mysql+pymysql://username:password@host:port/database_name
DATABASE_URL: str = "mysql+pymysql://root:taiyi1224@localhost:3306/baoxiang"
# 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"

View File

@ -29,6 +29,8 @@ else:
pool_recycle=db_settings.DB_POOL_RECYCLE,
echo=False,
future=True,
# 设置连接时使用的字符集
connect_args={"charset": "utf8mb4", "collation": "utf8mb4_unicode_ci"}
)
# 如果是MySQL启用严格模式
@ -37,9 +39,14 @@ if "mysql" in db_settings.DATABASE_URL:
def set_sqlite_pragma(dbapi_connection, connection_record):
"""
为MySQL连接设置参数如果使用MySQL
注意不再设置时区使用服务器本地时间以保持一致性
"""
pass
# 设置字符集为utf8mb4
try:
cursor = dbapi_connection.cursor()
cursor.execute("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci")
cursor.close()
except Exception:
pass
# 创建会话

View File

@ -374,20 +374,55 @@ class GameService:
)
# 平台抽水 - 直接到管理员账户
admin_user = UserService.get_admin_user(db)
if admin_user and platform_income > 0:
admin_user.balance += platform_income
db.commit()
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()
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}》平台抽水收益"
)
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

View File

@ -103,18 +103,32 @@ class StreamerService:
active_chests = db.query(func.count(Chest.id)).filter(
Chest.streamer_id == streamer.user_id,
Chest.status == "下注中"
Chest.status == 0 # BETTING状态
).scalar() or 0
# 计算总获奖(需要根据实际业务逻辑)
total_winnings = 0
# 查询已结算的宝箱以计算总奖金
settled_chests = db.query(Chest).filter(
Chest.streamer_id == streamer.user_id,
Chest.status == 3 # FINISHED状态
).all()
# 计算总获奖金额(所有已结算宝箱的奖金池总和)
total_winnings = sum(int(chest.pool_a + chest.pool_b) for chest in settled_chests)
# 计算抽成收益
total_commission = int(total_winnings * float(streamer.commission_rate) / 100)
# 计算平均宝箱价值
average_chest_value = int(total_winnings / total_chests) if total_chests > 0 else 0
# 计算成功率(这里简化处理)
success_rate = 0.75 # 示例值
# 计算成功率已结算宝箱中A选项获胜的比例
settled_count = len(settled_chests)
if settled_count > 0:
a_win_count = sum(1 for chest in settled_chests if chest.result == "A")
success_rate = a_win_count / settled_count
else:
# 如果没有已结算的宝箱,则使用默认值
success_rate = 0.0
return StreamerStatistics(
total_chests=total_chests,
@ -189,7 +203,7 @@ class StreamerService:
active_chests = db.query(func.count(Chest.id)).filter(
Chest.streamer_id == streamer.user_id,
Chest.status == "下注中"
Chest.status == 0 # BETTING状态
).scalar() or 0
max_chests = streamer.max_active_chests

View File

@ -5,7 +5,7 @@ import Loading from '../components/Loading';
import type { Transaction } from '../types';
const TransactionHistoryPage = () => {
const { user } = useAuth();
const { user: _user } = useAuth(); // 用户信息已在AuthContext中
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@ -29,30 +29,28 @@ const TransactionHistoryPage = () => {
};
const getTransactionTypeText = (type: string) => {
switch (type) {
case 'BET':
return '下注';
case 'WIN':
return '获胜奖励';
case '低保':
return '低保';
case '余额清零奖励':
return '余额清零奖励';
case 'ADMIN_ADJUST':
return '管理员调整';
case '管理员调整':
return '管理员调整';
case 'STREAMER_REVENUE':
return '主播分润';
case 'PLATFORM_REVENUE':
return '平台抽水';
case 'REGISTER':
return '新用户注册奖励';
case '注册奖励':
return '新用户注册奖励';
default:
return type;
}
// 统一交易类型映射,确保前后端一致性
const typeMap: Record<string, string> = {
'BET': '下注',
'WIN': '获胜奖励',
'下注': '下注',
'获胜利': '获胜利',
'WINNING': '获胜利',
'低保': '低保',
'ALLOWANCE': '低保',
'余额清零奖励': '余额清零奖励',
'ADMIN_ADJUST': '管理员调整',
'管理员调整': '管理员调整',
'ADMIN_OPERATION': '管理员调整',
'STREAMER_REVENUE': '主播分润',
'PLATFORM_REVENUE': '平台抽水',
'REGISTER': '新用户注册奖励',
'注册奖励': '新用户注册奖励',
'REGISTER_REWARD': '新用户注册奖励',
'NEW_USER_REWARD': '新用户注册奖励'
};
return typeMap[type] || type;
};
const formatDate = (dateString: string) => {
@ -91,14 +89,7 @@ const TransactionHistoryPage = () => {
</div>
)}
{loading ? (
<div className="text-center text-secondary" style={{ padding: '40px' }}>
<div className="spinner-border" role="status">
<span className="sr-only">...</span>
</div>
<p style={{ marginTop: '10px' }}>...</p>
</div>
) : transactions.length === 0 ? (
{transactions.length === 0 ? (
<div className="empty-state">
<div className="empty-state-icon">📊</div>
<h3></h3>

View File

@ -55,8 +55,8 @@ const UserProfile = () => {
setAllowanceInfo(allowanceData);
// 计算用户统计数据
const bets = txData.filter(tx => tx.type === 'BET');
const wins = txData.filter(tx => tx.type === 'WIN');
const bets = txData.filter(tx => tx.type === 'BET' || tx.type === '下注');
const wins = txData.filter(tx => tx.type === 'WIN' || tx.type === '获胜利' || tx.type === 'WINNING');
const totalBets = bets.length;
const totalWinnings = wins.reduce((sum, tx) => sum + tx.amount, 0);
const winRate = totalBets > 0 ? (wins.length / totalBets) * 100 : 0;
@ -92,30 +92,28 @@ const UserProfile = () => {
};
const getTransactionTypeText = (type: string) => {
switch (type) {
case 'BET':
return '下注';
case 'WIN':
return '获胜奖励';
case '低保':
return '低保';
case '余额清零奖励':
return '余额清零奖励';
case 'ADMIN_ADJUST':
return '管理员调整';
case '管理员调整':
return '管理员调整';
case 'STREAMER_REVENUE':
return '主播分润';
case 'PLATFORM_REVENUE':
return '平台抽水';
case 'REGISTER':
return '新用户注册奖励';
case '注册奖励':
return '新用户注册奖励';
default:
return type;
}
// 统一交易类型映射,确保前后端一致性
const typeMap: Record<string, string> = {
'BET': '下注',
'WIN': '获胜奖励',
'下注': '下注',
'获胜利': '获胜利',
'WINNING': '获胜利',
'低保': '低保',
'ALLOWANCE': '低保',
'余额清零奖励': '余额清零奖励',
'ADMIN_ADJUST': '管理员调整',
'管理员调整': '管理员调整',
'ADMIN_OPERATION': '管理员调整',
'STREAMER_REVENUE': '主播分润',
'PLATFORM_REVENUE': '平台抽水',
'REGISTER': '新用户注册奖励',
'注册奖励': '新用户注册奖励',
'REGISTER_REWARD': '新用户注册奖励',
'NEW_USER_REWARD': '新用户注册奖励'
};
return typeMap[type] || type;
};
const getRoleText = (role: string) => {
@ -327,14 +325,7 @@ const UserProfile = () => {
</button>
</div>
)}
{loading ? (
<div className="text-center text-secondary" style={{ padding: '20px' }}>
<div className="spinner-border" role="status">
<span className="sr-only">...</span>
</div>
<p style={{ marginTop: '10px' }}>...</p>
</div>
) : transactions.length === 0 ? (
{transactions.length === 0 ? (
<div className="empty-state">
<div className="empty-state-icon">📊</div>
<h4></h4>

View File

@ -172,9 +172,9 @@ export interface StreamerProfile {
// 主播统计数据
export interface StreamerStatistics {
total_chests: number;
total_bets: number;
total_pool: number;
total_earnings: number;
avg_participants: number;
active_chests: number;
total_winnings: number;
total_commission: number;
average_chest_value: number;
success_rate: number;
}