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

108 lines
14 KiB
PHP

<?php
declare(strict_types=1);
require dirname(__DIR__) . '/includes/bootstrap.php';
require dirname(__DIR__) . '/includes/game_engine.php';
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
function api_response(int $code, string $msg = 'ok', mixed $data = []): never
{
echo json_encode(['code' => $code, 'msg' => $msg, 'data' => $data], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
exit;
}
function api_user(array $user): array
{
global $config;
$direct=(float)setting('direct_commission_rate','10');$second=(float)setting('second_commission_rate','1');
$formatRate=static function(float $rate):string{$formatted=rtrim(rtrim(number_format($rate,4,'.',''),'0'),'.');return $formatted===''?'0':$formatted;};
$promotionRule='一级下线充值奖励 '.$formatRate($direct).'%,二级下线充值奖励 '.$formatRate($second).'%。';
$promotionUrl=$config['app_url'].'/index.php?view=register&invite='.rawurlencode((string)($user['invite_code']??''));
return ['id'=>(int)$user['id'],'user_id'=>(int)$user['id'],'balance'=>(float)$user['balance'],'img'=>'','href'=>$promotionUrl,'rule'=>setting('announcement'),'rule1'=>$promotionRule,'rule2'=>setting('recharge_instructions'),'interval'=>3,'wxNum'=>setting('customer_service'),'wx'=>setting('customer_service')];
}
function api_auth(): array
{
$user=current_user(true);
if(!$user) api_response(0,'请先登录',[]);
return $user;
}
try {
if($_SERVER['REQUEST_METHOD']!=='POST') api_response(0,'请求方式错误',[]);
$origin=(string)($_SERVER['HTTP_ORIGIN']??'');
$requestHost = explode(':', (string)($_SERVER['HTTP_HOST'] ?? ''))[0];
if($origin!=='' && parse_url($origin,PHP_URL_HOST)!==$requestHost) api_response(0,'非法请求',[]);
$path=(string)($_GET['path']??'');
$raw=(string)file_get_contents('php://input');$data=json_decode($raw,true);if(!is_array($data))$data=$_POST;
if($path==='/index/login/login'){
$username=trim((string)($data['username']??''));
if((string)($data['password']??'')==='__server_session__'){
$sessionUser=current_user(true);
if($sessionUser && hash_equals((string)$sessionUser['username'],$username))api_response(1,'登录成功',api_user($sessionUser));
}
assert_auth_not_limited('user_login',$username,5,900);
$stmt=db()->prepare('SELECT * FROM users WHERE username=?');$stmt->execute([$username]);$user=$stmt->fetch();
if(!$user||!password_verify((string)($data['password']??''),$user['password_hash'])){record_auth_attempt('user_login',$username,false);api_response(0,'账号或密码错误',[]);}if((int)$user['status']!==1)api_response(0,'账号已停用',[]);
session_regenerate_id(true);$_SESSION['user_id']=(int)$user['id'];record_auth_attempt('user_login',$username,true);db()->prepare('UPDATE users SET last_login_at=?,last_login_ip=? WHERE id=?')->execute([now(),client_ip(),$user['id']]);api_response(1,'登录成功',api_user($user));
}
if($path==='/index/login/register'){
$username=trim((string)($data['username']??''));assert_auth_not_limited('register',$username,5,3600);record_auth_attempt('register',$username,false);$password=(string)($data['password']??'');if(!preg_match('/^[A-Za-z0-9_]{6,20}$/',$username))api_response(0,'账号需为6-20位',[]);if(strlen($password)<8||strlen($password)>72)api_response(0,'密码需为8-72位',[]);create_user_account($username,$password,'',(string)($data['invite_code']??$data['invite']??$data['pid']??''));api_response(1,'注册成功',[]);
}
$user=api_auth();
if($path==='/index/login/user_info')api_response(1,'登录成功',api_user($user));
if($path==='/index/login/user_pwd'){
$old=(string)($data['j_pwd']??$data['oldpassword']??$data['old_password']??'');$new=(string)($data['x_pwd']??$data['password']??$data['newpassword']??'');if(!password_verify($old,$user['password_hash']))api_response(0,'原密码错误',[]);if(strlen($new)<8)api_response(0,'新密码至少8位',[]);db()->prepare('UPDATE users SET password_hash=?,updated_at=? WHERE id=?')->execute([password_hash($new,PASSWORD_DEFAULT),now(),$user['id']]);api_response(1,'修改成功',[]);
}
if($path==='/index/login/answer')api_response(1,'修改成功',[]);
if($path==='/index/login/modify')api_response(0,'请在会员中心修改密码',[]);
if($path==='/index/kalman/receive'){
$code=strtoupper(trim((string)($data['kalman_pwd']??'')));redeem_recharge_card((int)$user['id'],$code,'游戏内充值卡兑换');api_response(1,'充值成功',api_user(current_user(true)));
}
if($path==='/index/game/get_gift'){
if(setting('game_enabled','1')!=='1')api_response(0,'游戏维护中',[]);$stake=round((float)($data['money']??0),2);if($stake<=0||$stake>100000)api_response(0,'投入金额无效',[]);
$fruits=is_array($data['fruits']??null)?$data['fruits']:[];$allowedIds=[4,16,20,8,2,19,13,5];$validated=[];$calculatedStake=0.0;if(count($fruits)>8)api_response(0,'投入数据无效',[]);foreach($fruits as $item){if(!is_array($item)||count($item)<2)api_response(0,'投入数据无效',[]);$fruit=(int)$item[0];$amount=(float)$item[1];if(!in_array($fruit,$allowedIds,true)||$amount<=0||$amount>100000||abs($amount-round($amount))>0.001||isset($validated[$fruit]))api_response(0,'投入数据无效',[]);$validated[$fruit]=$amount;$calculatedStake+=$amount;}if(abs($calculatedStake-$stake)>0.001)api_response(0,'投入金额校验失败',[]);
$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([$user['id']]);$before=(float)$stmt->fetchColumn();if($before<$stake)throw new RuntimeException('余额不足');
$settings=settings_map();$outcome=game_select_outcome($pdo,$stake,$validated,$settings);$win=(float)$outcome['payout'];
$result=$outcome['type']>0?['type'=>$outcome['type'],'pos'=>['pos'=>$outcome['position'],'luck'=>$outcome['positions']]]:['type'=>0,'pos'=>$outcome['position']];
$after=$before-$stake+$win;$pdo->prepare('UPDATE users SET balance=?,updated_at=? WHERE id=?')->execute([$after,now(),$user['id']]);
$logResult=['result'=>$result,'outcome'=>$outcome,'target_rtp'=>(float)($settings['target_rtp']??92)];
$pdo->prepare('INSERT INTO game_logs(user_id,game_type,bet_amount,win_amount,profit,result_json,created_at) VALUES(?,?,?,?,?,?,?)')->execute([$user['id'],'spin',$stake,$win,$win-$stake,json_encode($logResult,JSON_UNESCAPED_UNICODE),now()]);
create_ledger($pdo,(int)$user['id'],'game',$win-$stake,$before,$after,$outcome['kind']==='special'?$outcome['name']:($outcome['kind']==='lucky_eat'?'LUCKY通吃':'普通开奖'));
$pendingSql=$pdo->getAttribute(PDO::ATTR_DRIVER_NAME)==='mysql'?'INSERT INTO game_pending_wins(user_id,amount,updated_at) VALUES(?,?,?) ON DUPLICATE KEY UPDATE amount=VALUES(amount),updated_at=VALUES(updated_at)':'INSERT INTO game_pending_wins(user_id,amount,updated_at) VALUES(?,?,?) ON CONFLICT(user_id) DO UPDATE SET amount=excluded.amount,updated_at=excluded.updated_at';
$pdo->prepare($pendingSql)->execute([$user['id'],$win,now()]);$pdo->commit();
}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}
api_response(1,'ok',['data'=>$result,'money'=>[$win]]);
}
if($path==='/index/game/size'){
$guess=(int)($data['size']??0);$requested=round((float)($data['balance']??0),2);if(!in_array($guess,[1,2],true)||$requested<=0)api_response(0,'比大小参数无效',[]);
$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([$user['id']]);$before=(float)$stmt->fetchColumn();
$stmt=$pdo->prepare('SELECT amount FROM game_pending_wins WHERE user_id=?'.$lock);$stmt->execute([$user['id']]);$pending=$stmt->fetchColumn();if($pending===false||abs((float)$pending-$requested)>0.001)throw new RuntimeException('该笔中奖已结算,请重新开始。');
$settings=settings_map();$winRate=min(100.0,max(0.0,(float)($settings['size_win_rate']??46)));$isWin=random_int(1,1000000)<=(int)round($winRate*10000);
if($isWin){$number=$guess===1?random_int(1,7):random_int(8,14);$after=$before+$requested;$payout=$requested*2;$nextPending=$payout;$delta=$requested;}else{$number=$guess===1?random_int(8,14):random_int(1,7);if($before<$requested)throw new RuntimeException('余额不足');$after=$before-$requested;$payout=0.0;$nextPending=0.0;$delta=-$requested;}
$pdo->prepare('UPDATE users SET balance=?,updated_at=? WHERE id=?')->execute([$after,now(),$user['id']]);$pdo->prepare('UPDATE game_pending_wins SET amount=?,updated_at=? WHERE user_id=?')->execute([$nextPending,now(),$user['id']]);
$detail=['type'=>'size','guess'=>$guess,'number'=>$number,'win'=>$isWin,'win_rate'=>$winRate];$pdo->prepare('INSERT INTO game_logs(user_id,game_type,bet_amount,win_amount,profit,result_json,created_at) VALUES(?,?,?,?,?,?,?)')->execute([$user['id'],'size',$requested,$payout,$delta,json_encode($detail,JSON_UNESCAPED_UNICODE),now()]);create_ledger($pdo,(int)$user['id'],'game_size',$delta,$before,$after,$isWin?'比大小获胜':'比大小失败');$pdo->commit();
}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}api_response(1,'ok',(string)$number);
}
if($path==='/index/game/gamelog'){$offset=max(0,(int)($data['offset']??0));$length=min(50,max(1,(int)($data['length']??10)));$stmt=db()->prepare('SELECT bet_amount AS money,created_at AS log_time,win_amount AS in_money,profit FROM game_logs WHERE user_id=? ORDER BY id DESC LIMIT ? OFFSET ?');$stmt->bindValue(1,(int)$user['id'],PDO::PARAM_INT);$stmt->bindValue(2,$length,PDO::PARAM_INT);$stmt->bindValue(3,$offset,PDO::PARAM_INT);$stmt->execute();$countStmt=db()->prepare('SELECT COUNT(*) FROM game_logs WHERE user_id=?');$countStmt->execute([$user['id']]);api_response(1,'ok',['data'=>$stmt->fetchAll(),'count'=>(int)$countStmt->fetchColumn()]);}
if($path==='/index/game/money'){$amount=round((float)($data['money']??0),2);$account=trim((string)($data['wx_id']??''));$name=trim((string)($data['name']??''));if($amount<100||abs(fmod($amount,100.0))>0.001)api_response(0,'提现金额需为100的倍数',[]);if($account===''||$name==='')api_response(0,'请填写收款账号和姓名',[]);$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([$user['id']]);$before=(float)$stmt->fetchColumn();if($before<$amount)throw new RuntimeException('余额不足');$after=$before-$amount;$pdo->prepare('UPDATE users SET balance=?,updated_at=? WHERE id=?')->execute([$after,now(),$user['id']]);$pdo->prepare("INSERT INTO withdrawal_orders(user_id,amount,account,real_name,status,created_at) VALUES(?,?,?,?,'pending',?)")->execute([$user['id'],$amount,mb_substr($account,0,100),mb_substr($name,0,50),now()]);create_ledger($pdo,(int)$user['id'],'withdrawal',-$amount,$before,$after,'提现申请','WD'.$pdo->lastInsertId());$pdo->prepare('UPDATE game_pending_wins SET amount=0,updated_at=? WHERE user_id=?')->execute([now(),$user['id']]);$pdo->commit();}catch(Throwable $e){if($pdo->inTransaction())$pdo->rollBack();throw $e;}api_response(1,'提交成功',[]);}
if($path==='/index/game/money_log'){$stmt=db()->prepare("SELECT id AS monery_id,created_at AS monery_time,amount,status AS state FROM withdrawal_orders WHERE user_id=? ORDER BY id DESC LIMIT 50");$stmt->execute([$user['id']]);$rows=$stmt->fetchAll();api_response(1,'ok',['data'=>$rows,'count'=>count($rows)]);}
if($path==='/index/game/distribution'){
$grade=(int)($data['grade']??0);$offset=max(0,(int)($data['offset']??0));$length=min(50,max(1,(int)($data['length']??10)));$pdo=db();
if($grade===0){
$countStmt=$pdo->prepare("SELECT COUNT(*) FROM users u JOIN referral_rewards r ON r.invitee_id=u.id AND r.inviter_id=? AND r.status IN ('rewarded','qualified')");$countStmt->execute([$user['id']]);$count=(int)$countStmt->fetchColumn();
$stmt=$pdo->prepare("SELECT u.username AS user_id,u.created_at AS fx_time,COALESCE(SUM(c.commission_amount),0) AS a_money,0 AS b_money FROM users u JOIN referral_rewards r ON r.invitee_id=u.id AND r.inviter_id=? AND r.status IN ('rewarded','qualified') LEFT JOIN recharge_commissions c ON c.recharging_user_id=u.id AND c.beneficiary_user_id=? AND c.level=1 AND c.reversed_at IS NULL GROUP BY u.id,u.username,u.created_at ORDER BY u.id DESC LIMIT ? OFFSET ?");$stmt->bindValue(1,(int)$user['id'],PDO::PARAM_INT);$stmt->bindValue(2,(int)$user['id'],PDO::PARAM_INT);$stmt->bindValue(3,$length,PDO::PARAM_INT);$stmt->bindValue(4,$offset,PDO::PARAM_INT);$stmt->execute();
}else{
$countStmt=$pdo->prepare("SELECT COUNT(*) FROM users u JOIN users p ON p.id=u.inviter_id JOIN referral_rewards rp ON rp.invitee_id=p.id AND rp.inviter_id=? AND rp.status IN ('rewarded','qualified') JOIN referral_rewards ru ON ru.invitee_id=u.id AND ru.inviter_id=p.id AND ru.status IN ('rewarded','qualified')");$countStmt->execute([$user['id']]);$count=(int)$countStmt->fetchColumn();
$stmt=$pdo->prepare("SELECT u.username AS user_id,u.created_at AS fx_time,0 AS a_money,COALESCE(SUM(c.commission_amount),0) AS b_money FROM users u JOIN users p ON p.id=u.inviter_id JOIN referral_rewards rp ON rp.invitee_id=p.id AND rp.inviter_id=? AND rp.status IN ('rewarded','qualified') JOIN referral_rewards ru ON ru.invitee_id=u.id AND ru.inviter_id=p.id AND ru.status IN ('rewarded','qualified') LEFT JOIN recharge_commissions c ON c.recharging_user_id=u.id AND c.beneficiary_user_id=? AND c.level=2 AND c.reversed_at IS NULL GROUP BY u.id,u.username,u.created_at ORDER BY u.id DESC LIMIT ? OFFSET ?");$stmt->bindValue(1,(int)$user['id'],PDO::PARAM_INT);$stmt->bindValue(2,(int)$user['id'],PDO::PARAM_INT);$stmt->bindValue(3,$length,PDO::PARAM_INT);$stmt->bindValue(4,$offset,PDO::PARAM_INT);$stmt->execute();
}
api_response(1,'ok',['data'=>$stmt->fetchAll(),'count'=>$count]);
}
api_response(1,'ok',[]);
} catch(Throwable $e){api_response(0,$e instanceof RuntimeException?$e->getMessage():'服务器错误',[]);}