100 lines
12 KiB
PHP
100 lines
12 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require dirname(__DIR__) . '/includes/bootstrap.php';
|
|
require dirname(__DIR__) . '/includes/game_engine.php';
|
|
$admin = require_admin();
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') redirect('/admin/index.php');
|
|
$return = (string)($_POST['return'] ?? '/admin/index.php');
|
|
if (!str_starts_with($return, '/admin/')) $return = '/admin/index.php';
|
|
|
|
try {
|
|
verify_csrf();
|
|
$action = (string)($_POST['action'] ?? '');
|
|
if ($action === 'logout') {
|
|
admin_audit(db(), 'admin_logout', 'admin', (int)$admin['id']);
|
|
unset($_SESSION['admin_id']); session_regenerate_id(true); redirect('/admin/login.php');
|
|
}
|
|
if ($action === 'review_recharge') {
|
|
$id = (int)($_POST['id'] ?? 0); $decision = (string)($_POST['decision'] ?? '');
|
|
if (!in_array($decision, ['approved','rejected'], true)) throw new RuntimeException('审核操作无效。');
|
|
$pdo = db(); $pdo->beginTransaction();
|
|
try {
|
|
$lock = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'mysql' ? ' FOR UPDATE' : '';
|
|
$stmt=$pdo->prepare('SELECT * FROM recharge_orders WHERE id=?'.$lock);$stmt->execute([$id]);$order=$stmt->fetch();
|
|
if(!$order || $order['status']!=='pending') throw new RuntimeException('订单已处理或不存在。');
|
|
if($decision==='approved'){
|
|
$stmt=$pdo->prepare('SELECT balance FROM users WHERE id=?'.$lock);$stmt->execute([$order['user_id']]);$before=(float)$stmt->fetchColumn();$after=$before+(float)$order['amount'];
|
|
$pdo->prepare('UPDATE users SET balance=?, updated_at=? WHERE id=?')->execute([$after,now(),$order['user_id']]);
|
|
create_ledger($pdo,(int)$order['user_id'],'recharge',(float)$order['amount'],$before,$after,'充值审核到账',$order['order_no']);
|
|
credit_recharge_commissions($pdo,(int)$order['user_id'],(float)$order['amount'],'order',(string)$order['order_no']);
|
|
}
|
|
$pdo->prepare('UPDATE recharge_orders SET status=?, admin_note=?, reviewed_by=?, reviewed_at=? WHERE id=?')->execute([$decision,mb_substr(trim((string)($_POST['note']??'')),0,200),$_SESSION['admin_id'],now(),$id]);
|
|
admin_audit($pdo, 'recharge_review', 'recharge_order', $id, ['status'=>'pending'], ['status'=>$decision,'amount'=>(float)$order['amount']]);
|
|
$pdo->commit();
|
|
} catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
|
|
flash('success',$decision==='approved'?'订单已通过并到账。':'订单已拒绝。');
|
|
} elseif ($action === 'reverse_recharge') {
|
|
reverse_recharge_order(db(), (int)($_POST['id']??0), (string)($_POST['note']??''));
|
|
flash('success','充值及关联佣金已完成冲正。');
|
|
} elseif ($action === 'review_withdrawal') {
|
|
$id=(int)($_POST['id']??0);$decision=(string)($_POST['decision']??'');if(!in_array($decision,['approved','rejected'],true))throw new RuntimeException('审核操作无效。');$pdo=db();$pdo->beginTransaction();try{$lock=$pdo->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql'?' FOR UPDATE':'';$stmt=$pdo->prepare('SELECT * FROM withdrawal_orders WHERE id=?'.$lock);$stmt->execute([$id]);$order=$stmt->fetch();if(!$order||$order['status']!=='pending')throw new RuntimeException('订单已处理或不存在。');if($decision==='rejected'){$stmt=$pdo->prepare('SELECT balance FROM users WHERE id=?'.$lock);$stmt->execute([$order['user_id']]);$before=(float)$stmt->fetchColumn();$after=$before+(float)$order['amount'];$pdo->prepare('UPDATE users SET balance=?,updated_at=? WHERE id=?')->execute([$after,now(),$order['user_id']]);create_ledger($pdo,(int)$order['user_id'],'withdrawal_refund',(float)$order['amount'],$before,$after,'提现拒绝退回','WD'.$id);}$pdo->prepare('UPDATE withdrawal_orders SET status=? WHERE id=?')->execute([$decision,$id]);admin_audit($pdo,'withdrawal_review','withdrawal_order',$id,['status'=>'pending'],['status'=>$decision,'amount'=>(float)$order['amount']]);$pdo->commit();}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}flash('success',$decision==='approved'?'提现已通过。':'提现已拒绝并退回余额。');
|
|
} elseif ($action === 'adjust_balance') {
|
|
$userId=(int)($_POST['user_id']??0);$amount=round((float)($_POST['amount']??0),2);
|
|
if($amount===0.0 || abs($amount)>1000000) throw new RuntimeException('调整金额无效。');
|
|
$pdo=db();$pdo->beginTransaction();
|
|
try{$lock=$pdo->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql'?' FOR UPDATE':'';$stmt=$pdo->prepare('SELECT balance FROM users WHERE id=?'.$lock);$stmt->execute([$userId]);$value=$stmt->fetchColumn();if($value===false)throw new RuntimeException('用户不存在。');$before=(float)$value;$after=$before+$amount;if($after<0)throw new RuntimeException('调整后余额不能小于 0。');$pdo->prepare('UPDATE users SET balance=?,updated_at=? WHERE id=?')->execute([$after,now(),$userId]);create_ledger($pdo,$userId,'admin_adjust',$amount,$before,$after,'后台手动调整');admin_audit($pdo,'balance_adjust','user',$userId,['balance'=>$before],['balance'=>$after,'amount'=>$amount]);$pdo->commit();}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
|
|
flash('success','余额已调整。');
|
|
} elseif ($action === 'toggle_user') {
|
|
$userId=(int)($_POST['user_id']??0);$status=(int)($_POST['status']??0)===1?1:0;$pdo=db();$pdo->prepare('UPDATE users SET status=?,updated_at=? WHERE id=?')->execute([$status,now(),$userId]);admin_audit($pdo,'user_status','user',$userId,null,['status'=>$status]);flash('success',$status?'账号已启用。':'账号已停用。');
|
|
} elseif ($action === 'create_cards') {
|
|
$amount = round((float)($_POST['amount'] ?? 0), 2);
|
|
$cardType = (string)($_POST['card_type'] ?? 'single');
|
|
if (!in_array($cardType, ['single', 'shared'], true)) throw new RuntimeException('充值卡类型无效。');
|
|
$count = $cardType === 'shared' ? 1 : min(100, max(1, (int)($_POST['count'] ?? 1)));
|
|
if ($amount <= 0 || $amount > 100000) throw new RuntimeException('卡面额无效。');
|
|
$pdo = db();
|
|
$codes = [];
|
|
$pdo->beginTransaction();
|
|
try {
|
|
$stmt = $pdo->prepare('INSERT INTO recharge_cards(code,amount,card_type,status,created_at) VALUES(?,?,?,?,?)');
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$code = ($cardType === 'shared' ? 'GX' : 'JG').strtoupper(bin2hex(random_bytes(6)));
|
|
$stmt->execute([$code, $amount, $cardType, $cardType === 'shared' ? 'active' : 'unused', now()]);
|
|
$codes[] = $code;
|
|
}
|
|
admin_audit($pdo, 'cards_create', 'recharge_card', '', ['amount'=>$amount, 'count'=>$count, 'card_type'=>$cardType]);
|
|
$pdo->commit();
|
|
} catch (Throwable $e) {
|
|
if ($pdo->inTransaction()) $pdo->rollBack();
|
|
throw $e;
|
|
}
|
|
$_SESSION['generated_card_codes'] = $codes;
|
|
flash('success', $cardType === 'shared' ? '已生成公共充值卡,每个账号可兑换一次。' : "已生成 {$count} 张充值卡,并自动选中本批卡号。");
|
|
} elseif ($action === 'toggle_shared_card') {
|
|
$cardId = (int)($_POST['id'] ?? 0);
|
|
$status = (string)($_POST['status'] ?? '');
|
|
if (!in_array($status, ['active', 'disabled'], true)) throw new RuntimeException('公共卡状态无效。');
|
|
$pdo = db();
|
|
$stmt = $pdo->prepare("UPDATE recharge_cards SET status=? WHERE id=? AND card_type='shared'");
|
|
$stmt->execute([$status, $cardId]);
|
|
if ($stmt->rowCount() !== 1) throw new RuntimeException('公共充值卡不存在。');
|
|
admin_audit($pdo, 'shared_card_status', 'recharge_card', $cardId, null, ['status'=>$status]);
|
|
flash('success', $status === 'active' ? '公共充值卡已启用。' : '公共充值卡已停用。');
|
|
} elseif ($action === 'game_settings') {
|
|
$target=max(1.0,min(200.0,(float)($_POST['target_rtp']??92)));$window=max(100,min(100000,(int)($_POST['rtp_window']??1000)));$sizeRate=max(0.0,min(100.0,(float)($_POST['size_win_rate']??46)));$luckyEatRate=round(max(0.0,min(100.0,(float)($_POST['lucky_eat_rate']??2))),4);
|
|
$values=['target_rtp'=>(string)$target,'rtp_window'=>(string)$window,'size_win_rate'=>(string)$sizeRate,'lucky_eat_rate'=>(string)$luckyEatRate];
|
|
foreach(['bar','77','xx','xg','ld','nm','jz','pg'] as $key){foreach(['low','high'] as $level){$field='odds_'.$key.'_'.$level;$value=round((float)($_POST[$field]??0),4);if($value<0||$value>1000)throw new RuntimeException('普通赔率需在 0-1000 之间。');$values[$field]=(string)$value;}}
|
|
for($position=1;$position<=24;$position++){$field='normal_weight_'.$position;$value=round((float)($_POST[$field]??1),4);if($value<0.01||$value>1000)throw new RuntimeException('普通格子权重需在 0.01-1000 之间。');$values[$field]=(string)$value;}
|
|
$totalRate=0.0;$specialRtp=0.0;foreach(['xsy','dsy','dsx','tnsh','tlbb','shuaidao','huoche','dmg'] as $key){$rate=round((float)($_POST['special_'.$key.'_rate']??0),4);$odds=round((float)($_POST['special_'.$key.'_odds']??0),4);if($rate<0||$rate>100)throw new RuntimeException('特殊玩法触发率需在 0%-100% 之间。');if($odds<0||$odds>1000)throw new RuntimeException('特殊玩法倍数需在 0-1000 之间。');$values['special_'.$key.'_rate']=(string)$rate;$values['special_'.$key.'_odds']=(string)$odds;$totalRate+=$rate;$specialRtp+=$rate*$odds;}
|
|
if($totalRate+$luckyEatRate>=100)throw new RuntimeException('LUCKY 通吃率与特殊玩法总触发率之和必须小于 100%。');if($specialRtp>$target+0.0001)throw new RuntimeException('特殊玩法理论 RTP '.number_format($specialRtp,2).'% 已超过目标 RTP,请降低触发率或倍数。');
|
|
$pdo=db();$before=settings_map();game_probability_model(array_replace($before,$values));$driver=$pdo->getAttribute(PDO::ATTR_DRIVER_NAME);$sql=$driver==='mysql'?'INSERT INTO settings(`key`,`value`) VALUES(?,?) ON DUPLICATE KEY UPDATE `value`=VALUES(`value`)':'INSERT INTO settings(`key`,`value`) VALUES(?,?) ON CONFLICT(`key`) DO UPDATE SET `value`=excluded.`value`';$pdo->beginTransaction();try{$stmt=$pdo->prepare($sql);foreach($values as $key=>$value)$stmt->execute([$key,$value]);admin_audit($pdo,'game_settings','settings','game',array_intersect_key($before,$values),$values);$pdo->commit();}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}flash('success','玩法概率、格子权重与 RTP 设置已生效。');
|
|
} elseif ($action === 'settings') {
|
|
$inviteReward=round((float)($_POST['invite_reward']??0),2);if($inviteReward<0||$inviteReward>1000000)throw new RuntimeException('单人邀请奖励需在 0-1000000 之间。');$_POST['invite_reward']=(string)$inviteReward;
|
|
foreach(['direct_commission_rate','second_commission_rate'] as $rateKey){$rate=round((float)($_POST[$rateKey]??0),4);if($rate<0||$rate>100)throw new RuntimeException('充值佣金比例需在 0%-100% 之间。');$_POST[$rateKey]=(string)$rate;}
|
|
$keys=['site_name','announcement','customer_service','recharge_instructions','register_bonus','invite_reward','direct_commission_rate','second_commission_rate','game_enabled'];$pdo=db();$before=settings_map();$driver=$pdo->getAttribute(PDO::ATTR_DRIVER_NAME);$sql=$driver==='mysql'?'INSERT INTO settings(`key`,`value`) VALUES(?,?) ON DUPLICATE KEY UPDATE `value`=VALUES(`value`)':'INSERT INTO settings(`key`,`value`) VALUES(?,?) ON CONFLICT(`key`) DO UPDATE SET `value`=excluded.`value`';$stmt=$pdo->prepare($sql);$after=[];foreach($keys as $key){$after[$key]=mb_substr(trim((string)($_POST[$key]??'')),0,1000);$stmt->execute([$key,$after[$key]]);}admin_audit($pdo,'site_settings','settings','site',array_intersect_key($before,$after),$after);flash('success','站点设置已保存。');
|
|
} elseif ($action === 'admin_password') {
|
|
$admin=current_admin();$pdo=db();$stmt=$pdo->prepare('SELECT password_hash FROM admins WHERE id=?');$stmt->execute([$admin['id']]);$hash=(string)$stmt->fetchColumn();$old=(string)($_POST['old_password']??'');$new=(string)($_POST['new_password']??'');if(!password_verify($old,$hash))throw new RuntimeException('原密码不正确。');if(strlen($new)<12||strlen($new)>72)throw new RuntimeException('新密码需为 12-72 位。');$pdo->prepare('UPDATE admins SET password_hash=? WHERE id=?')->execute([password_hash($new,PASSWORD_DEFAULT),$admin['id']]);admin_audit($pdo,'admin_password','admin',(int)$admin['id']);flash('success','管理员密码已更新。');
|
|
} else throw new RuntimeException('无效的操作。');
|
|
} catch(Throwable $e){flash('error',$e instanceof RuntimeException?$e->getMessage():'操作失败,请稍后重试。');}
|
|
redirect($return);
|