Files
shuiguoji/admin/action.php
T
2026-07-23 08:58:13 +08:00

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);