feat: 重构代码架构 + 新增报表/跟单/输赢统计功能
- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController - 新增 Service 层: TransactionService, ReportService, FollowPlanService - pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll) - 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan) - 后台新增跟单计划管理 + 用户输赢明细统计 - 封盘状态显示倒计时 (x:xx) - 音效仅在开奖弹窗打开时播放 - 路由按模块分组整理 - autoload 支持 App\Services 命名空间
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use Db\Database;
|
||||
|
||||
class FollowPlanService {
|
||||
private $db;
|
||||
|
||||
public function __construct(Database $db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有启用的跟单计划(前台展示)
|
||||
*/
|
||||
public function getActivePlans(int $gameId = 0): array
|
||||
{
|
||||
$where = ['status' => 1];
|
||||
if ($gameId > 0) $where['game_id'] = $gameId;
|
||||
$where['ORDER'] = ['id' => 'ASC'];
|
||||
|
||||
$plans = $this->db->select('follow_plans', '*', $where);
|
||||
foreach ($plans as &$plan) {
|
||||
$stats = $this->getPlanStats($plan['id']);
|
||||
$plan['total_records'] = $stats['total'];
|
||||
$plan['win_count'] = $stats['wins'];
|
||||
$plan['win_rate'] = $stats['total'] > 0 ? round($stats['wins'] / $stats['total'] * 100, 1) : 0;
|
||||
// 最近 10 期记录
|
||||
$plan['recent'] = $this->db->select('follow_plan_records', '*', [
|
||||
'plan_id' => $plan['id'],
|
||||
'is_win[!]' => null,
|
||||
'ORDER' => ['id' => 'DESC'],
|
||||
'LIMIT' => 10
|
||||
]);
|
||||
}
|
||||
unset($plan);
|
||||
return $plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划统计
|
||||
*/
|
||||
public function getPlanStats(int $planId): array
|
||||
{
|
||||
$total = $this->db->count('follow_plan_records', [
|
||||
'plan_id' => $planId,
|
||||
'is_win[!]' => null
|
||||
]);
|
||||
$wins = $this->db->count('follow_plan_records', [
|
||||
'plan_id' => $planId,
|
||||
'is_win' => 1
|
||||
]);
|
||||
return ['total' => $total, 'wins' => $wins];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户的跟单状态
|
||||
*/
|
||||
public function getUserFollowStatus(int $userId): array
|
||||
{
|
||||
$rows = $this->db->select('user_follow_plans', '*', [
|
||||
'user_id' => $userId,
|
||||
'is_active' => 1
|
||||
]);
|
||||
$result = [];
|
||||
foreach ($rows as $r) {
|
||||
$result[$r['plan_id']] = $r;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户开始/停止跟单
|
||||
*/
|
||||
public function toggleFollow(int $userId, int $planId): array
|
||||
{
|
||||
$existing = $this->db->get('user_follow_plans', '*', [
|
||||
'user_id' => $userId,
|
||||
'plan_id' => $planId
|
||||
]);
|
||||
|
||||
if ($existing) {
|
||||
$newStatus = $existing['is_active'] ? 0 : 1;
|
||||
$this->db->update('user_follow_plans', ['is_active' => $newStatus], [
|
||||
'id' => $existing['id']
|
||||
]);
|
||||
return ['success' => true, 'active' => $newStatus];
|
||||
}
|
||||
|
||||
// 新建
|
||||
$plan = $this->db->get('follow_plans', '*', ['id' => $planId, 'status' => 1]);
|
||||
if (!$plan) {
|
||||
return ['success' => false, 'message' => '计划不存在或已禁用'];
|
||||
}
|
||||
|
||||
$this->db->insert('user_follow_plans', [
|
||||
'user_id' => $userId,
|
||||
'plan_id' => $planId,
|
||||
'is_active' => 1,
|
||||
]);
|
||||
return ['success' => true, 'active' => 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户跟单汇总(总胜率、总盈亏)
|
||||
*/
|
||||
public function getUserSummary(int $userId): array
|
||||
{
|
||||
$pdo = $this->db->medoo->pdo;
|
||||
$totalWinRate = 0;
|
||||
$totalProfit = 0;
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT SUM(total_bets) as bets, SUM(total_wins) as wins, SUM(total_profit) as profit
|
||||
FROM user_follow_plans WHERE user_id = ?
|
||||
");
|
||||
$stmt->execute([$userId]);
|
||||
$row = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$bets = intval($row['bets'] ?? 0);
|
||||
$wins = intval($row['wins'] ?? 0);
|
||||
$totalWinRate = $bets > 0 ? round($wins / $bets * 100, 1) : 0;
|
||||
$totalProfit = floatval($row['profit'] ?? 0);
|
||||
} catch (\Exception $e) {}
|
||||
|
||||
return ['win_rate' => $totalWinRate, 'profit' => $totalProfit];
|
||||
}
|
||||
|
||||
// ===== 后台管理 =====
|
||||
|
||||
/**
|
||||
* 获取所有计划(后台)
|
||||
*/
|
||||
public function getAllPlans(): array
|
||||
{
|
||||
$plans = $this->db->select('follow_plans', '*', ['ORDER' => ['id' => 'DESC']]);
|
||||
foreach ($plans as &$plan) {
|
||||
$stats = $this->getPlanStats($plan['id']);
|
||||
$plan['total_records'] = $stats['total'];
|
||||
$plan['win_count'] = $stats['wins'];
|
||||
$plan['win_rate'] = $stats['total'] > 0 ? round($stats['wins'] / $stats['total'] * 100, 1) : 0;
|
||||
$plan['follower_count'] = $this->db->count('user_follow_plans', [
|
||||
'plan_id' => $plan['id'], 'is_active' => 1
|
||||
]);
|
||||
}
|
||||
unset($plan);
|
||||
return $plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建/更新计划
|
||||
*/
|
||||
public function savePlan(array $data): array
|
||||
{
|
||||
$fields = [
|
||||
'game_id' => intval($data['game_id'] ?? 1),
|
||||
'name' => trim($data['name'] ?? ''),
|
||||
'plan_type' => trim($data['plan_type'] ?? 'bs'),
|
||||
'target_rank' => intval($data['target_rank'] ?? 1),
|
||||
'strategy' => trim($data['strategy'] ?? 'follow'),
|
||||
'bet_amount' => floatval($data['bet_amount'] ?? 100),
|
||||
'status' => intval($data['status'] ?? 1),
|
||||
];
|
||||
|
||||
if (empty($fields['name'])) {
|
||||
return ['success' => false, 'message' => '计划名称不能为空'];
|
||||
}
|
||||
|
||||
$id = intval($data['id'] ?? 0);
|
||||
if ($id > 0) {
|
||||
$this->db->update('follow_plans', $fields, ['id' => $id]);
|
||||
} else {
|
||||
$fields['created_by'] = intval($data['created_by'] ?? 0);
|
||||
$this->db->insert('follow_plans', $fields);
|
||||
$id = intval($this->db->id());
|
||||
}
|
||||
|
||||
return ['success' => true, 'id' => $id];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除计划
|
||||
*/
|
||||
public function deletePlan(int $planId): array
|
||||
{
|
||||
$this->db->delete('follow_plan_records', ['plan_id' => $planId]);
|
||||
$this->db->delete('user_follow_plans', ['plan_id' => $planId]);
|
||||
$this->db->delete('follow_plans', ['id' => $planId]);
|
||||
return ['success' => true];
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加计划推荐记录(由 cron 或后台手动触发)
|
||||
*/
|
||||
public function addRecord(int $planId, int $periodId, string $periodNumber, string $recommendValue): array
|
||||
{
|
||||
$exists = $this->db->get('follow_plan_records', 'id', [
|
||||
'plan_id' => $planId, 'period_id' => $periodId
|
||||
]);
|
||||
if ($exists) {
|
||||
return ['success' => false, 'message' => '该期已有推荐记录'];
|
||||
}
|
||||
|
||||
$this->db->insert('follow_plan_records', [
|
||||
'plan_id' => $planId,
|
||||
'period_id' => $periodId,
|
||||
'period_number' => $periodNumber,
|
||||
'recommend_value' => $recommendValue,
|
||||
]);
|
||||
return ['success' => true, 'id' => intval($this->db->id())];
|
||||
}
|
||||
|
||||
/**
|
||||
* 结算推荐记录(开奖后调用)
|
||||
*/
|
||||
public function settleRecord(int $recordId, string $resultValue, bool $isWin): void
|
||||
{
|
||||
$this->db->update('follow_plan_records', [
|
||||
'result_value' => $resultValue,
|
||||
'is_win' => $isWin ? 1 : 0,
|
||||
], ['id' => $recordId]);
|
||||
|
||||
// 更新所有跟了这个计划的用户统计
|
||||
$record = $this->db->get('follow_plan_records', '*', ['id' => $recordId]);
|
||||
if (!$record) return;
|
||||
|
||||
$plan = $this->db->get('follow_plans', '*', ['id' => $record['plan_id']]);
|
||||
if (!$plan) return;
|
||||
|
||||
$betAmount = floatval($plan['bet_amount']);
|
||||
$profit = $isWin ? $betAmount * 0.95 : -$betAmount; // 简化盈亏计算
|
||||
|
||||
$followers = $this->db->select('user_follow_plans', '*', [
|
||||
'plan_id' => $record['plan_id'], 'is_active' => 1
|
||||
]);
|
||||
|
||||
foreach ($followers as $f) {
|
||||
$this->db->update('user_follow_plans', [
|
||||
'total_bets[+]' => 1,
|
||||
'total_wins[+]' => $isWin ? 1 : 0,
|
||||
'total_profit[+]' => $profit,
|
||||
], ['id' => $f['id']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use Db\Database;
|
||||
|
||||
class ReportService {
|
||||
private $db;
|
||||
|
||||
public function __construct(Database $db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 前台用户报表:按投注类型分组统计
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @param string $dateFrom 开始日期 (Y-m-d)
|
||||
* @param string $dateTo 结束日期 (Y-m-d)
|
||||
* @return array
|
||||
*/
|
||||
public function getUserReport(int $userId, string $dateFrom, string $dateTo): array
|
||||
{
|
||||
$rangeFrom = $dateFrom . ' 00:00:00';
|
||||
$rangeTo = $dateTo . ' 23:59:59';
|
||||
|
||||
$pdo = $this->db->medoo->pdo;
|
||||
try {
|
||||
// 投注统计(有效流水 = 已结算投注额,排除 pending)
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
bet_type as type,
|
||||
COUNT(*) as bet_count,
|
||||
SUM(amount) as amount,
|
||||
SUM(CASE WHEN status IN ('win','lose') THEN amount ELSE 0 END) as effective_flow,
|
||||
SUM(CASE
|
||||
WHEN status='win' THEN win_amount
|
||||
WHEN status='lose' THEN -amount
|
||||
ELSE 0
|
||||
END) as win_loss
|
||||
FROM bets
|
||||
WHERE user_id = ? AND created_at BETWEEN ? AND ?
|
||||
GROUP BY bet_type
|
||||
ORDER BY bet_type
|
||||
");
|
||||
$stmt->execute([$userId, $rangeFrom, $rangeTo]);
|
||||
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// 退水(代理返佣给该用户的金额)
|
||||
$rebateTotal = 0;
|
||||
try {
|
||||
$stmt2 = $pdo->prepare("
|
||||
SELECT COALESCE(SUM(commission), 0) as total
|
||||
FROM agent_commissions
|
||||
WHERE from_user_id = ? AND type = 'rebate' AND created_at BETWEEN ? AND ?
|
||||
");
|
||||
$stmt2->execute([$userId, $rangeFrom, $rangeTo]);
|
||||
$rebateTotal = floatval($stmt2->fetch(\PDO::FETCH_ASSOC)['total'] ?? 0);
|
||||
} catch (\Exception $e) {
|
||||
$rebateTotal = 0;
|
||||
}
|
||||
|
||||
// 将退水按比例分配到各类型(或全部放在第一行)
|
||||
$totalBet = array_sum(array_column($rows, 'amount'));
|
||||
foreach ($rows as &$row) {
|
||||
$row['amount'] = floatval($row['amount']);
|
||||
$row['effective_flow'] = floatval($row['effective_flow']);
|
||||
$row['win_loss'] = floatval($row['win_loss']);
|
||||
// 按投注额占比分配退水
|
||||
$ratio = $totalBet > 0 ? $row['amount'] / $totalBet : 0;
|
||||
$row['rebate'] = round($rebateTotal * $ratio, 2);
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 后台总体统计
|
||||
*
|
||||
* @param string $dateFrom 开始日期 (Y-m-d)
|
||||
* @param string $dateTo 结束日期 (Y-m-d)
|
||||
* @return array
|
||||
*/
|
||||
public function getOverviewStats(string $dateFrom, string $dateTo): array
|
||||
{
|
||||
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
|
||||
$pdo = $this->db->medoo->pdo;
|
||||
|
||||
// 总投注(排除虚拟)
|
||||
$totalBet = $this->db->sum('bets', 'amount', [
|
||||
'is_virtual' => 0, 'created_at[<>]' => $range
|
||||
]) ?: 0;
|
||||
|
||||
// 总派奖
|
||||
$totalWin = $this->db->sum('bets', 'win_amount', [
|
||||
'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range
|
||||
]) ?: 0;
|
||||
|
||||
// 平台利润
|
||||
$profit = $totalBet - $totalWin;
|
||||
$profitRate = $totalBet > 0 ? round($profit / $totalBet * 100, 2) : 0;
|
||||
|
||||
// 总充值
|
||||
$totalDeposit = $this->db->sum('transactions', 'amount', [
|
||||
'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0,
|
||||
'created_at[<>]' => $range
|
||||
]) ?: 0;
|
||||
|
||||
// 总提现
|
||||
$totalWithdraw = abs($this->db->sum('transactions', 'amount', [
|
||||
'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0,
|
||||
'created_at[<>]' => $range
|
||||
]) ?: 0);
|
||||
|
||||
// 代理佣金
|
||||
$totalCommission = $this->db->sum('agent_commissions', 'commission', [
|
||||
'created_at[<>]' => $range
|
||||
]) ?: 0;
|
||||
|
||||
// 反水
|
||||
$totalRebate = $this->db->sum('agent_commissions', 'commission', [
|
||||
'type' => 'rebate', 'created_at[<>]' => $range
|
||||
]) ?: 0;
|
||||
|
||||
// 用户统计
|
||||
$totalUsers = $this->db->count('users', ['is_virtual' => 0]);
|
||||
$newUsers = $this->db->count('users', [
|
||||
'is_virtual' => 0, 'created_at[<>]' => $range
|
||||
]);
|
||||
|
||||
// 活跃用户数(有投注记录的非虚拟用户)
|
||||
$activeUsers = 0;
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT COUNT(DISTINCT user_id) as cnt FROM bets WHERE is_virtual = 0 AND created_at BETWEEN ? AND ?");
|
||||
$stmt->execute([$range[0], $range[1]]);
|
||||
$activeUsers = (int)($stmt->fetch(\PDO::FETCH_ASSOC)['cnt'] ?? 0);
|
||||
} catch (\Exception $e) {
|
||||
$activeUsers = 0;
|
||||
}
|
||||
|
||||
return compact(
|
||||
'totalBet', 'totalWin', 'profit', 'profitRate',
|
||||
'totalDeposit', 'totalWithdraw', 'totalCommission', 'totalRebate',
|
||||
'totalUsers', 'newUsers', 'activeUsers'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日明细统计
|
||||
*
|
||||
* @param string $dateFrom 开始日期 (Y-m-d)
|
||||
* @param string $dateTo 结束日期 (Y-m-d)
|
||||
* @return array
|
||||
*/
|
||||
public function getDailyStats(string $dateFrom, string $dateTo): array
|
||||
{
|
||||
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
|
||||
$pdo = $this->db->medoo->pdo;
|
||||
|
||||
$dailyStats = [];
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT
|
||||
DATE(created_at) as date,
|
||||
SUM(amount) as total_bet,
|
||||
SUM(CASE WHEN status='win' THEN win_amount ELSE 0 END) as total_win,
|
||||
COUNT(*) as bet_count,
|
||||
SUM(CASE WHEN status='win' THEN 1 ELSE 0 END) as win_count
|
||||
FROM bets
|
||||
WHERE is_virtual = 0 AND created_at BETWEEN ? AND ?
|
||||
GROUP BY DATE(created_at)
|
||||
ORDER BY date DESC
|
||||
");
|
||||
$stmt->execute([$range[0], $range[1]]);
|
||||
$dailyStats = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 补充每日充提数据
|
||||
foreach ($dailyStats as &$day) {
|
||||
$dr = [$day['date'] . ' 00:00:00', $day['date'] . ' 23:59:59'];
|
||||
$day['deposits'] = $this->db->sum('transactions', 'amount', [
|
||||
'is_virtual' => 0, 'type[~]' => '%deposit%', 'amount[>]' => 0,
|
||||
'created_at[<>]' => $dr
|
||||
]) ?: 0;
|
||||
$day['withdraws'] = abs($this->db->sum('transactions', 'amount', [
|
||||
'is_virtual' => 0, 'type[~]' => '%withdraw%', 'amount[<]' => 0,
|
||||
'created_at[<>]' => $dr
|
||||
]) ?: 0);
|
||||
$day['total_bet'] = floatval($day['total_bet'] ?? 0);
|
||||
$day['total_win'] = floatval($day['total_win'] ?? 0);
|
||||
$day['bet_count'] = intval($day['bet_count'] ?? 0);
|
||||
$day['win_count'] = intval($day['win_count'] ?? 0);
|
||||
$day['profit'] = $day['total_bet'] - $day['total_win'];
|
||||
$day['win_rate'] = $day['bet_count'] > 0 ? round($day['win_count'] / $day['bet_count'] * 100, 1) : 0;
|
||||
}
|
||||
unset($day);
|
||||
|
||||
return $dailyStats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户输赢明细(含日维度分组 + 用户汇总)
|
||||
*
|
||||
* @param string $dateFrom 开始日期 (Y-m-d)
|
||||
* @param string $dateTo 结束日期 (Y-m-d)
|
||||
* @return array ['raw' => 原始行, 'summary' => 按用户汇总]
|
||||
*/
|
||||
public function getUserWinLoss(string $dateFrom, string $dateTo): array
|
||||
{
|
||||
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
|
||||
$pdo = $this->db->medoo->pdo;
|
||||
|
||||
$userWinLoss = [];
|
||||
try {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT u.id as user_id, u.username, u.balance,
|
||||
DATE(b.created_at) as date,
|
||||
SUM(b.amount) as total_bet,
|
||||
SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as total_win,
|
||||
COUNT(*) as bet_count,
|
||||
SUM(CASE WHEN b.status='win' THEN 1 ELSE 0 END) as win_count
|
||||
FROM bets b
|
||||
JOIN users u ON b.user_id = u.id
|
||||
WHERE b.is_virtual = 0 AND b.created_at BETWEEN ? AND ?
|
||||
GROUP BY b.user_id, DATE(b.created_at)
|
||||
ORDER BY u.username, date DESC
|
||||
");
|
||||
$stmt->execute([$range[0], $range[1]]);
|
||||
$userWinLoss = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
return ['raw' => [], 'summary' => []];
|
||||
}
|
||||
|
||||
// 按用户汇总
|
||||
$userSummary = [];
|
||||
foreach ($userWinLoss as $row) {
|
||||
$uid = $row['user_id'];
|
||||
if (!isset($userSummary[$uid])) {
|
||||
$userSummary[$uid] = [
|
||||
'username' => $row['username'],
|
||||
'balance' => floatval($row['balance']),
|
||||
'total_bet' => 0, 'total_win' => 0,
|
||||
'bet_count' => 0, 'win_count' => 0,
|
||||
'days' => []
|
||||
];
|
||||
}
|
||||
$bet = floatval($row['total_bet']);
|
||||
$win = floatval($row['total_win']);
|
||||
$userSummary[$uid]['total_bet'] += $bet;
|
||||
$userSummary[$uid]['total_win'] += $win;
|
||||
$userSummary[$uid]['bet_count'] += intval($row['bet_count']);
|
||||
$userSummary[$uid]['win_count'] += intval($row['win_count']);
|
||||
$userSummary[$uid]['days'][] = [
|
||||
'date' => $row['date'],
|
||||
'bet' => $bet, 'win' => $win,
|
||||
'profit' => $bet - $win,
|
||||
'count' => intval($row['bet_count']),
|
||||
];
|
||||
}
|
||||
// 排序:按总投注额降序
|
||||
uasort($userSummary, function($a, $b) { return $b['total_bet'] <=> $a['total_bet']; });
|
||||
|
||||
return ['raw' => $userWinLoss, 'summary' => $userSummary];
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户排行(投注额 TOP N)
|
||||
*
|
||||
* @param string $dateFrom 开始日期 (Y-m-d)
|
||||
* @param string $dateTo 结束日期 (Y-m-d)
|
||||
* @param int $limit 条数限制,0=不限
|
||||
* @return array
|
||||
*/
|
||||
public function getTopUsers(string $dateFrom, string $dateTo, int $limit = 20): array
|
||||
{
|
||||
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
|
||||
$pdo = $this->db->medoo->pdo;
|
||||
|
||||
$sql = "
|
||||
SELECT u.username,
|
||||
SUM(b.amount) as total_bet,
|
||||
SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as total_win,
|
||||
SUM(b.amount) - SUM(CASE WHEN b.status='win' THEN b.win_amount ELSE 0 END) as profit,
|
||||
COUNT(*) as bet_count
|
||||
FROM bets b
|
||||
JOIN users u ON b.user_id = u.id
|
||||
WHERE b.is_virtual = 0 AND b.created_at BETWEEN ? AND ?
|
||||
GROUP BY b.user_id
|
||||
ORDER BY total_bet DESC
|
||||
";
|
||||
if ($limit > 0) {
|
||||
$sql .= " LIMIT " . intval($limit);
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute([$range[0], $range[1]]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
} catch (\Exception $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 代理报表
|
||||
*
|
||||
* @param string $dateFrom 开始日期 (Y-m-d)
|
||||
* @param string $dateTo 结束日期 (Y-m-d)
|
||||
* @return array
|
||||
*/
|
||||
public function getAgentStats(string $dateFrom, string $dateTo): array
|
||||
{
|
||||
$range = [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59'];
|
||||
|
||||
$agentStats = [];
|
||||
$agents = $this->db->select('agents', '*', ['status' => 1]);
|
||||
foreach ($agents as $ag) {
|
||||
$playerIds = $this->db->select('users', 'id', ['agent_id' => $ag['id'], 'is_virtual' => 0]);
|
||||
$agBet = 0;
|
||||
$agWin = 0;
|
||||
if (!empty($playerIds)) {
|
||||
$agBet = $this->db->sum('bets', 'amount', ['user_id' => $playerIds, 'is_virtual' => 0, 'created_at[<>]' => $range]) ?: 0;
|
||||
$agWin = $this->db->sum('bets', 'win_amount', ['user_id' => $playerIds, 'is_virtual' => 0, 'status' => 'win', 'created_at[<>]' => $range]) ?: 0;
|
||||
}
|
||||
$agComm = $this->db->sum('agent_commissions', 'commission', ['agent_id' => $ag['id'], 'created_at[<>]' => $range]) ?: 0;
|
||||
$agentStats[] = [
|
||||
'agent' => $ag,
|
||||
'user' => $this->db->get('users', ['username'], ['id' => $ag['user_id']]),
|
||||
'players' => count($playerIds),
|
||||
'bets' => $agBet,
|
||||
'wins' => $agWin,
|
||||
'commission' => $agComm,
|
||||
'profit' => $agBet - $agWin - $agComm,
|
||||
];
|
||||
}
|
||||
|
||||
return $agentStats;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace App\Services;
|
||||
|
||||
use Db\Database;
|
||||
|
||||
class TransactionService {
|
||||
private $db;
|
||||
|
||||
public function __construct(Database $db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户间转账
|
||||
*
|
||||
* @param int $fromUserId 转出用户ID
|
||||
* @param string $toUsername 收款用户名
|
||||
* @param float $amount 转账金额
|
||||
* @return array ['success' => bool, 'message' => string]
|
||||
*/
|
||||
public function transfer(int $fromUserId, string $toUsername, float $amount): array
|
||||
{
|
||||
$toUsername = trim($toUsername);
|
||||
|
||||
if ($toUsername === '') {
|
||||
return ['success' => false, 'message' => '请输入对方用户名'];
|
||||
}
|
||||
if ($amount <= 0) {
|
||||
return ['success' => false, 'message' => '金额无效'];
|
||||
}
|
||||
|
||||
$fromUser = $this->db->get('users', '*', ['id' => $fromUserId]);
|
||||
if (!$fromUser) {
|
||||
return ['success' => false, 'message' => '用户不存在'];
|
||||
}
|
||||
if ($fromUser['balance'] < $amount) {
|
||||
return ['success' => false, 'message' => '余额不足'];
|
||||
}
|
||||
|
||||
// 查找收款用户
|
||||
$toUser = $this->db->get('users', '*', ['username' => $toUsername]);
|
||||
if (!$toUser) {
|
||||
return ['success' => false, 'message' => '收款用户不存在'];
|
||||
}
|
||||
if ($toUser['id'] === $fromUser['id']) {
|
||||
return ['success' => false, 'message' => '不能转给自己'];
|
||||
}
|
||||
|
||||
$this->db->medoo->pdo->beginTransaction();
|
||||
try {
|
||||
$now = date('Y-m-d H:i:s');
|
||||
$fromBalanceBefore = floatval($fromUser['balance']);
|
||||
$toBalanceBefore = floatval($toUser['balance']);
|
||||
$fromBalanceAfter = $fromBalanceBefore - $amount;
|
||||
$toBalanceAfter = $toBalanceBefore + $amount;
|
||||
|
||||
// 扣款
|
||||
$this->db->update('users', ['balance' => $fromBalanceAfter, 'updated_at' => $now], ['id' => $fromUser['id']]);
|
||||
// 加款
|
||||
$this->db->update('users', ['balance' => $toBalanceAfter, 'updated_at' => $now], ['id' => $toUser['id']]);
|
||||
|
||||
// 转出流水
|
||||
$this->db->insert('transactions', [
|
||||
'user_id' => $fromUser['id'],
|
||||
'type' => 'transfer_out',
|
||||
'amount' => -$amount,
|
||||
'balance_before' => $fromBalanceBefore,
|
||||
'balance_after' => $fromBalanceAfter,
|
||||
'description' => '转账给 ' . $toUser['username'],
|
||||
'created_at' => $now,
|
||||
]);
|
||||
// 转入流水
|
||||
$this->db->insert('transactions', [
|
||||
'user_id' => $toUser['id'],
|
||||
'type' => 'transfer_in',
|
||||
'amount' => $amount,
|
||||
'balance_before' => $toBalanceBefore,
|
||||
'balance_after' => $toBalanceAfter,
|
||||
'description' => '收到 ' . $fromUser['username'] . ' 的转账',
|
||||
'created_at' => $now,
|
||||
]);
|
||||
|
||||
$this->db->medoo->pdo->commit();
|
||||
return ['success' => true, 'message' => '转账成功'];
|
||||
} catch (\Exception $e) {
|
||||
$this->db->medoo->pdo->rollBack();
|
||||
return ['success' => false, 'message' => '系统错误'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 充提申请
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @param string $type 类型: deposit|withdraw
|
||||
* @param float $amount 金额
|
||||
* @param string $remark 备注
|
||||
* @return array ['success' => bool, 'message' => string]
|
||||
*/
|
||||
public function fundRequest(int $userId, string $type, float $amount, string $remark = ''): array
|
||||
{
|
||||
if (!in_array($type, ['deposit', 'withdraw'])) {
|
||||
return ['success' => false, 'message' => '无效类型'];
|
||||
}
|
||||
if ($amount <= 0) {
|
||||
return ['success' => false, 'message' => '金额无效'];
|
||||
}
|
||||
|
||||
$user = $this->db->get('users', '*', ['id' => $userId]);
|
||||
if (!$user) {
|
||||
return ['success' => false, 'message' => '用户不存在'];
|
||||
}
|
||||
|
||||
if ($type === 'withdraw') {
|
||||
if ($user['balance'] < $amount) {
|
||||
return ['success' => false, 'message' => '余额不足'];
|
||||
}
|
||||
$this->db->medoo->pdo->beginTransaction();
|
||||
try {
|
||||
$this->db->update('users', ['balance[-]' => $amount], ['id' => $user['id']]);
|
||||
$this->db->insert('fund_requests', [
|
||||
'user_id' => $user['id'],
|
||||
'type' => 'withdraw',
|
||||
'amount' => $amount,
|
||||
'status' => 'pending',
|
||||
'remark' => $remark,
|
||||
]);
|
||||
$this->db->medoo->pdo->commit();
|
||||
return ['success' => true, 'message' => '提现申请已提交'];
|
||||
} catch (\Exception $e) {
|
||||
$this->db->medoo->pdo->rollBack();
|
||||
return ['success' => false, 'message' => '系统错误'];
|
||||
}
|
||||
}
|
||||
|
||||
// deposit - 记录申请
|
||||
$this->db->insert('fund_requests', [
|
||||
'user_id' => $user['id'],
|
||||
'type' => 'deposit',
|
||||
'amount' => $amount,
|
||||
'status' => 'pending',
|
||||
'remark' => $remark,
|
||||
]);
|
||||
return ['success' => true, 'message' => '充值申请已提交'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user