diff --git a/app/admin/controller/user/MoneyLog.php b/app/admin/controller/user/MoneyLog.php index ac55286..e05ed75 100644 --- a/app/admin/controller/user/MoneyLog.php +++ b/app/admin/controller/user/MoneyLog.php @@ -265,4 +265,287 @@ class MoneyLog extends Backend 'report_table' => $finalReport ]); } + + public function dailyReport() + { + // 1. 从配置中获取动态的小游戏列表 + $games = config('mini_game') ?: []; + + // 2. 接收筛选参数,默认展示当月数据 + $start = $this->request->param('start/s', date('Y-m-01')); + $end = $this->request->param('end/s', date('Y-m-t')); + $type = $this->request->param('type/s', 'daily'); // daily, monthly, yearly + + // 转换时间戳范围 + $startTime = strtotime($start . ' 00:00:00'); + $endTime = strtotime($end . ' 23:59:59'); + + // 根据 type 动态决定 MySQL 的日期分组格式 + $dateFormat = '%Y-%m-%d'; + if ($type === 'monthly') $dateFormat = '%Y-%m'; + if ($type === 'yearly') $dateFormat = '%Y'; + + // 3. 🌟 动态生成小游戏的 SQL 统计子句 + $promoSqlPieces = []; + foreach ($games as $gameId => $gameName) { + // 过滤掉不合法的非数字KEY,防止 SQL 注入风险 + $gameId = (int)$gameId; + + // 顺便给生成的列取个易读的别名,比如:game_268_count, game_268_total + $promoSqlPieces[] = "COUNT(CASE WHEN game_type = {$gameId} THEN 1 END) AS game_{$gameId}_count"; + $promoSqlPieces[] = "SUM(CASE WHEN game_type = {$gameId} THEN amount ELSE 0 END) AS game_{$gameId}_total"; + } + + // 将动态生成的片段用逗号拼接到一起 + $dynamicPromoSelect = !empty($promoSqlPieces) ? ', ' . implode(', ', $promoSqlPieces) : ''; + + // 4. 核心聚合 SQL 1:基于余额变动表统计 + $moneyLogSql = " + SELECT + FROM_UNIXTIME(create_time, '{$dateFormat}') AS report_date, + COUNT(CASE WHEN type = 1 THEN 1 END) AS deposit_count, + SUM(CASE WHEN type = 1 THEN money ELSE 0 END) / 100 AS deposit_total, + COUNT(CASE WHEN type = 2 THEN 1 END) AS withdraw_count, + SUM(CASE WHEN type = 2 THEN money ELSE 0 END) / 100 AS withdraw_total, + (SUM(CASE WHEN type = 1 THEN money ELSE 0 END) - SUM(CASE WHEN type = 2 THEN money ELSE 0 END)) / 100 AS win_lose, + COUNT(CASE WHEN label = 2 THEN 1 END) AS unclaim_count, + SUM(CASE WHEN label = 2 THEN money ELSE 0 END) / 100 AS unclaim_total, + COUNT(DISTINCT user_id) AS active_member, + COUNT(DISTINCT CASE WHEN label = 1 THEN user_id END) AS new_deposit + FROM ba_user_money_log + WHERE create_time BETWEEN {$startTime} AND {$endTime} + GROUP BY report_date + "; + $moneyData = Db::query($moneyLogSql); + + // 5. 核心聚合 SQL 2:👉 注入动态字段,统计小游戏奖励 + $promoSql = " + SELECT + FROM_UNIXTIME(create_time, '{$dateFormat}') AS report_date + {$dynamicPromoSelect} -- 🌟 动态生成的 COUNT 和 SUM 语句放在这里 + FROM ba_promo_reward + WHERE create_time BETWEEN {$startTime} AND {$endTime} + GROUP BY report_date + "; + $promoData = Db::query($promoSql); + + // 6. 新注册用户统计 + $userRegisterSql = " + SELECT + FROM_UNIXTIME(create_time, '{$dateFormat}') AS report_date, + COUNT(id) AS new_register + FROM ba_user + WHERE create_time BETWEEN {$startTime} AND {$endTime} + GROUP BY report_date + "; + $registerData = class_exists('\think\facade\Db') ? Db::query($userRegisterSql) : []; + + // 7. 数据集结构重组与动态初始化 + $reportMap = []; + $current = $startTime; + while ($current <= $endTime) { + $dateStr = date(str_replace('%', '', $dateFormat), $current); + + // 构建一天的基础数据结构 + $baseRow = [ + 'date' => $dateStr, + 'deposit_count' => 0, 'deposit_total' => 0.00, + 'withdraw_count' => 0, 'withdraw_total' => 0.00, + 'win_lose' => 0.00, + 'unclaim_count' => 0, 'unclaim_total' => 0.00, + 'active_member' => 0, + 'new_deposit' => 0, + 'new_register' => 0, + ]; + + // 🌟 动态初始化游戏字段的初始值(全部设为 0) + foreach ($games as $gameId => $gameName) { + $baseRow["game_{$gameId}_count"] = 0; + $baseRow["game_{$gameId}_total"] = 0.00; + } + + $reportMap[$dateStr] = $baseRow; + $current = strtotime("+1 day", $current); + } + + // 合并资产变动数据 + foreach ($moneyData as $row) { + if (isset($reportMap[$row['report_date']])) { + $reportMap[$row['report_date']] = array_merge($reportMap[$row['report_date']], $row); + } + } + + // 合并动态小游戏数据 + foreach ($promoData as $row) { + if (isset($reportMap[$row['report_date']])) { + $reportMap[$row['report_date']] = array_merge($reportMap[$row['report_date']], $row); + } + } + + // 合并注册用户数据 + foreach ($registerData as $row) { + if (isset($reportMap[$row['report_date']])) { + $reportMap[$row['report_date']]['new_register'] = $row['new_register']; + } + } + $list = array_values($reportMap); + // 9. 返回结果给前端组件 + $this->success('Success', [ + 'start' => $start, + 'end' => $end, + 'type' => $type, + 'games' => $games, // 🌟 把游戏映射送给前端,方便前端循环生成表格的 header 列 + 'list' => $list, + ]); + } + + public function customerReport() + { + // 1. 获取动态的小游戏配置 + // 示例: [268 => 'plinko ball', 269 => 'smash eggs', 270 => 'spin wheel'] + $games = config('mini_game') ?: []; + + // 2. 接收前端筛选参数 + $start = $this->request->param('start/s', ''); + $end = $this->request->param('end/s', ''); + $username = $this->request->param('username/s', ''); + $loseRebate = $this->request->param('lose_rebate/f', 0); // 输值返利百分比,如输入 5 代表 5% + $limit = $this->request->param('limit/d', 10); // 🌟 每页条数,默认10条 + + // 3. 构建用户主表的查询条件 + $userWhere = []; + if (!empty($username)) { + $userWhere[] = ['username', 'LIKE', '%' . (string)$username . '%']; + } + + if (!empty($start) || !empty($end)) { + $startTime = !empty($start) ? strtotime($start . ' 00:00:00') : 0; + $endTime = !empty($end) ? strtotime($end . ' 23:59:59') : time(); + $userWhere[] = ['create_time', 'between', [$startTime, $endTime]]; + } + + // 4. 查询基础用户列表 (对应图片中的基本行) + // 🌟 假设你的用户主表去前缀叫 'user',如果不是请更换成实际表名 + $users = Db::name('user') + ->where($userWhere) + ->field('id, username, create_time') + ->order('create_time', 'desc') // 对应图片中最新注册的用户排在最上方 + ->paginate($limit); + + $count = $users->total(); + $currentPage = $users->currentPage(); + $lastPage = $users->lastPage(); + + // 如果没查到用户,直接返回空列表,免去后续的多表聚合 + if (empty($users->items())) { + $this->success('Success', [ + 'start' => $start, + 'end' => $end, + 'username' => $username, + 'games' => $games, + 'list' => [], + 'count' => $count, + 'current_page' => $currentPage, + 'last_page' => $lastPage, + ]); + } + // 提取当前页/当前筛选出的所有用户 ID 集合 + $userIds = array_column($users->items(), 'id'); + $userIdsStr = implode(',', $userIds); + + // 5. 🌟 聚合资金流水数据 (Deposit / Withdraw / WinLose) + $moneyLogSql = " + SELECT + user_id, + COUNT(CASE WHEN type = 1 THEN 1 END) AS deposit_count, + SUM(CASE WHEN type = 1 THEN money ELSE 0 END) / 100 AS deposit_total, + COUNT(CASE WHEN type = 2 THEN 1 END) AS withdraw_count, + SUM(CASE WHEN type = 2 THEN money ELSE 0 END) / 100 AS withdraw_total, + (SUM(CASE WHEN type = 1 THEN money ELSE 0 END) - SUM(CASE WHEN type = 2 THEN money ELSE 0 END)) / 100 AS win_lose + FROM ba_user_money_log + WHERE user_id IN ({$userIdsStr}) + GROUP BY user_id + "; + $moneyData = Db::query($moneyLogSql); + // 重组为以 user_id 为键的关联数组,方便后续读取 + $moneySummary = array_column($moneyData, null, 'user_id'); + + // 6. 🌟 动态拼装并聚合小游戏奖励数据 (与配置中的 gameId 挂钩) + $promoSummary = []; + if (!empty($games)) { + $promoPieces = []; + foreach ($games as $gameId => $name) { + $gameId = (int)$gameId; + $promoPieces[] = "COUNT(CASE WHEN game_type = {$gameId} THEN 1 END) AS game_{$gameId}_count"; + $promoPieces[] = "SUM(CASE WHEN game_type = {$gameId} THEN amount ELSE 0 END) AS game_{$gameId}_total"; + } + + $promoSql = "SELECT user_id, " . implode(', ', $promoPieces) . " + FROM ba_promo_reward + WHERE user_id IN ({$userIdsStr}) + GROUP BY user_id"; + $promoData = Db::query($promoSql); + $promoSummary = array_column($promoData, null, 'user_id'); + } + + // 7. 🌟 聚合下线推荐人数 (REFERRAL Downline) + $referralSql = "SELECT parent_id, COUNT(id) AS referral_count + FROM ba_user + WHERE parent_id IN ({$userIdsStr}) + GROUP BY parent_id"; + $referralData = Db::query($referralSql); + $referralSummary = array_column($referralData, null, 'parent_id'); + + // 8. 🌟 循环用户主表,拼装全维度报表行 + $list = []; + $loseRebatePercent = floatval($loseRebate) / 100; // 转换为小数进行百分比计算 + + foreach ($users as $user) { + $uid = $user['id']; + + // 提取资金数据,若该用户没充提过,则给一套默认清零数据 + $money = $moneySummary[$uid] ?? [ + 'deposit_count' => 0, 'deposit_total' => 0.00, + 'withdraw_count' => 0, 'withdraw_total' => 0.00, + 'win_lose' => 0.00 + ]; + + // 提取下线推荐人数 + $referralCount = $referralSummary[$uid]['referral_count'] ?? 0; + + // 计算输值返利:只有当 WIN/LOSE 为负数(代表用户输钱)且前端传了返利比时才计算 + $winLose = $money['win_lose']; + $calculatedRebate = 0.00; + if ($winLose < 0 && $loseRebatePercent > 0) { + $calculatedRebate = abs($winLose) * $loseRebatePercent; + } + + // 组装用户基础维度的列 + $row = [ + 'register_date' => date('Y-m-d H:i:s', $user['create_time']), + 'username' => $user['username'], + 'deposit_count' => $money['deposit_count'], + 'deposit_total' => $money['deposit_total'], + 'withdraw_count' => $money['withdraw_count'], + 'withdraw_total' => $money['withdraw_total'], + 'win_lose' => $winLose, + 'lose_rebate' => $calculatedRebate, + 'referral_count' => $referralCount, + ]; + + // 🌟 动态注入配置文件中每一个小游戏的数据列 + foreach ($games as $gameId => $name) { + $row["game_{$gameId}_count"] = $promoSummary[$uid]["game_{$gameId}_count"] ?? 0; + $row["game_{$gameId}_total"] = $promoSummary[$uid]["game_{$gameId}_total"] ?? 0.00; + } + + $list[] = $row; + } + + // 9. 返回给前端 + $this->success('Success', compact( + 'start', 'end', 'username', 'games', 'list', + 'count', 'currentPage', 'lastPage' + )); + } } \ No newline at end of file diff --git a/app/admin/controller/user/User.php b/app/admin/controller/user/User.php index b73670a..a22a060 100644 --- a/app/admin/controller/user/User.php +++ b/app/admin/controller/user/User.php @@ -2,6 +2,7 @@ namespace app\admin\controller\user; +use app\admin\model\PromoReward; use app\common\service\Jk8Services; use Throwable; use app\common\controller\Backend; @@ -100,7 +101,10 @@ class User extends Backend } catch (Throwable $e) { $this->error($e->getMessage()); } - + if (!empty($data['referrer_code'])) { + $parentId = $this->model->where('jk_username', $data['referrer_code'])->value('id'); + $data['parent_id'] = $parentId; + } $this->model->startTrans(); try { $data['jk_user_id'] = $jkId; @@ -226,4 +230,96 @@ class User extends Backend $this->success('', $data); } + public function submittedReward() + { + $games = config('mini_game') ?: []; + $rawStart = request()->param('start/s'); + $start = $rawStart ? strtotime($rawStart) : strtotime('today'); + $rawEnd = request()->param('end/s'); + $end = $rawEnd ? strtotime("$rawEnd +1 day") : strtotime('tomorrow'); + $status = $this->request->param('status'); + $username = $this->request->param('username'); + $gameId = $this->request->param('game_id'); + $limit = request()->param('limit/d') ?? 15; + $where = []; + if ($start > $end) { + list($start, $end) = [$end, $start]; + } + if (!empty($gameId)) { + $where[] = ['game_type', '=', $gameId]; + } + + if (!empty($status)) { + $where[] = ['status', '=', $status]; + } + + $query = PromoReward::with(['user']); + + if (!empty($username)) { + $has = UserModel::where('jk_username', 'LIKE', '%'. $username . '%'); + $query->hasWhere('user', $has); + } + + $list = $query->where($where) + ->whereBetweenTime('create_time', $start, $end)->order('create_time', 'desc')->paginate($limit); + + $list->each(function($item) use ($games) { + $item->submitted_time = date('Y-m-d H:i:s', $item->update_time); + $item->username = $item->user->jk_username ?? 'Unknown'; + $gameName = $games[$item->game_type] ?? 'Game'; + $item->reward_claim = $gameName . ' AUD' . $item->amount; + + if ($item->status == 1) { + $item->status_text = 'Approve Reward (by JDK API (Manual))'; + } elseif ($item->status == 2){ + $item->status_text = 'Reject Reward'; + } else { + $item->status_text = 'In process'; + } + unset($item->user); + }); + $this->success('', [ + 'game_id' => $gameId, + 'games' => $games, + 'list' => $list + ]); + } + public function editReward() + { + $pk = $this->model->getPk(); + $id = $this->request->param($pk); + $promoReward = new PromoReward(); + $row = $promoReward->find($id); + if (!$row) { + $this->error(__('Record not found')); + } + + $status = $this->request->post('status'); + + if (!$status || !in_array($status, [1, 2])) { + $this->error(__('Parameter %s can not be empty', [''])); + } + + $result = false; + $promoReward->startTrans(); + try { + if ($status == 1) { + $user = UserModel::find($row['user_id']); + $this->jk8Services->setScore($user['jk_username'], $row['amount']); + } + $result = $row->save([ + 'status' => (int)$status, + 'update_time' => time(), + ]); + $promoReward->commit(); + } catch (Throwable $e) { + $promoReward->rollback(); + $this->error($e->getMessage()); + } + if ($result !== false) { + $this->success(__('Update successful')); + } else { + $this->error(__('No rows updated')); + } + } } \ No newline at end of file diff --git a/app/admin/model/PromoReward.php b/app/admin/model/PromoReward.php new file mode 100644 index 0000000..da60291 --- /dev/null +++ b/app/admin/model/PromoReward.php @@ -0,0 +1,32 @@ +belongsTo(User::class, 'user_id'); + } + +} \ No newline at end of file diff --git a/app/admin/model/UserScore.php b/app/admin/model/UserScore.php index 2ca3f94..27b2571 100644 --- a/app/admin/model/UserScore.php +++ b/app/admin/model/UserScore.php @@ -2,11 +2,9 @@ namespace app\admin\model; -use think\Exception; use think\model; use think\model\relation\BelongsTo; use Throwable; -use app\admin\model\UserScoreLog; /** * UserScoreLog 模型 @@ -29,6 +27,11 @@ class UserScore extends model if (bccomp($before, $after, 2) !== 0) { // 计算分值变动(after - before) $change = bcsub($after, $before); + if ($change >= 0) { + $type = 1; + } else { + $type = 2; + } $userScoreLog = new UserScoreLog(); $userScoreLog->save([ 'user_id' => $model->user_id, @@ -36,7 +39,9 @@ class UserScore extends model 'before' => $before, 'after' => $after, 'score' => $change, - 'memo' => '管理员手动调整', // 建议增加备注字段区分来源 + 'memo' => $model->memo ?? '', + 'type' => $type, + 'created_by'=> $model->created_by ?? null, ]); } } diff --git a/app/admin/model/UserScoreLog.php b/app/admin/model/UserScoreLog.php index 57f6155..2d5d462 100644 --- a/app/admin/model/UserScoreLog.php +++ b/app/admin/model/UserScoreLog.php @@ -18,10 +18,9 @@ class UserScoreLog extends model protected $updateTime = false; const GAME_TYPE_LIST = [ - 1001 => 'Lucky Wheel', // 大转盘 - 1002 => 'Golden Eggs', // 砸金蛋 - 1003 => 'Daily Mission', // 每日任务 - 1004 => 'Plinko Ball', // 普林科弹球 + 268 => 'plinko ball', + 269 => 'smash eggs', + 270 => 'spin wheel', ]; public static function onBeforeDelete(): bool @@ -37,13 +36,9 @@ class UserScoreLog extends model public function getGameTypeTextAttr($value, $data) { $gameType = $data['game_type'] ?? 0; - - // 如果需要严格的多语言,可以在这里根据 $lang 切换,或者直接配合系统的 lang() 助手函数 - // 这里我们先直接返回对应的标准国际化名称 return self::GAME_TYPE_LIST[$gameType] ?? 'Unknown Game'; } - // 3. 别忘了把获取器追加到模型的可见输出数组里 protected $append = ['game_type_text']; } \ No newline at end of file diff --git a/app/api/controller/External.php b/app/api/controller/External.php index f092e35..32809ff 100644 --- a/app/api/controller/External.php +++ b/app/api/controller/External.php @@ -2,34 +2,99 @@ namespace app\api\controller; -use Throwable; -use ba\Captcha; -use ba\ClickCaptcha; -use think\facade\Validate; +use app\admin\model\PromoReward; +use app\common\controller\Api; +use app\common\model\Config; +use app\common\service\Jk8Services; use app\common\model\User; -use app\common\library\Email; -use app\common\controller\Frontend; -use PHPMailer\PHPMailer\Exception as PHPMailerException; -class External extends Frontend +class External extends Api { - protected array $noNeedLogin = ['depositNotify']; - + protected $jk8Services; public function initialize(): void { parent::initialize(); + $this->jk8Services = app(Jk8Services::class); } - public function depositNotify(): void + public function depositNotify() { + $appSecret = config('xyx')['app_secret']; + $appSecret = '4a01e8dfaa93b69723e937c04962af26'; + $expireSeconds = 300; + $requestParams = $this->request->param(); - - try { - - } catch (PHPMailerException) { - + if (!isset($requestParams['sign']) || !isset($requestParams['timestamp'])) { + $this->success(__('Missing sign or timestamp')); } - $this->success(__('Mail sent successfully~')); + $currentTime = time(); + if (abs($currentTime - intval($requestParams['timestamp'])) > $expireSeconds) { + $this->success(__('Request has expired')); + } + + $receivedSign = $requestParams['sign']; + unset($requestParams['sign']); + + ksort($requestParams); + + $stringToBeSigned = http_build_query($requestParams) . '&app_secret=' . $appSecret; + + $calculatedSign = hash('sha256', $stringToBeSigned); + + if (!hash_equals($calculatedSign, $receivedSign)) { + return json(['status'=> 'FAIL']); + } + $user = User::where('xyx_account', $requestParams['account'])->find(); +// $transactionId = $this->jk8Services->setScore($user['jk_username'], $requestParams['prize_amount']); + $promoReward = new PromoReward; + $data = [ + 'user_id' => $user['id'], + 'game_type' => $requestParams['game_id'], + 'amount' => $requestParams['prize_amount'], + ]; + $promoReward->save($data); + return json(['status'=> 'OK']); + } + + public function claimPrizeNotify() + { + $config = Config::whereIn('name', ['app_id', 'app_secret'])->where('group', 'basics')->column('value', 'name'); + $appSecret = $config['app_secret']; + $expireSeconds = 300; + $requestParams = $this->request->param(); + + if (!isset($requestParams['sign']) || !isset($requestParams['timestamp'])) { + $this->success(__('Missing sign or timestamp')); + } + + $currentTime = time(); + if (abs($currentTime - intval($requestParams['timestamp'])) > $expireSeconds) { + $this->success(__('Request has expired')); + } + + $receivedSign = $requestParams['sign']; + unset($requestParams['sign']); + + + ksort($requestParams); + + $stringToBeSigned = http_build_query($requestParams) . '&app_secret=' . $appSecret; + + $calculatedSign = hash('sha256', $stringToBeSigned); + + if (!hash_equals($calculatedSign, $receivedSign)) { + return json(['status'=> 'FAIL']); + } + $user = User::where('xyx_account', $requestParams['account'])->find(); +// $transactionId = $this->jk8Services->setScore($user['jk_username'], $requestParams['prize_amount']); + $promoReward = new PromoReward; + $data = [ + 'user_id' => $user['id'], + 'game_type' => $requestParams['game_id'], + 'amount' => $requestParams['prize_amount'], + ]; + $promoReward->save($data); + return json(['status'=> 'OK']); } } \ No newline at end of file diff --git a/app/api/route/app.php b/app/api/route/app.php index 248a37c..74ed93c 100644 --- a/app/api/route/app.php +++ b/app/api/route/app.php @@ -12,7 +12,7 @@ Route::group('external', function () { Route::group('external', function () { // 这里面的所有路由都会经过 ApiAuth 中间件 - Route::post('test', 'Account/test'); Route::post('user-withdraw', 'Withdraw/apply'); +// Route::post('claimPrizeNotify', 'External/claimPrizeNotify'); })->middleware(ApiAuth::class); \ No newline at end of file diff --git a/app/common/middleware/ApiAuth.php b/app/common/middleware/ApiAuth.php index fdbfc99..f8e9ecd 100644 --- a/app/common/middleware/ApiAuth.php +++ b/app/common/middleware/ApiAuth.php @@ -15,14 +15,12 @@ namespace app\common\middleware; use app\common\model\Config; use Closure; use think\facade\Cache; -use think\exception\HttpException; - class ApiAuth { protected $apps; public function handle($request, Closure $next) { - $this->apps = Config::whereIn('name', ['appKey', 'appSecret'])->column('value', 'name'); + $this->apps = Config::whereIn('name', ['app_id', 'app_secret'])->where('group', 'basics')->column('value', 'name'); $params = $request->param(); $appKey = $request->header('X-Api-AppKey'); $timestamp = $request->header('X-Api-Timestamp'); @@ -31,29 +29,29 @@ class ApiAuth // 1. 基础检查 if (!$signature || !$appKey || !$timestamp || !$nonce) { - throw new HttpException(401, '缺少必要的签名参数'); + return response('缺少必要的签名参数', 401); } // 2. 检查 AppKey 是否合法 - if ($this->apps['appKey'] != $appKey) { - throw new HttpException(401, '无效的 AppKey'); + if ($this->apps['app_id'] != $appKey) { + return response('无效的 AppKey', 401); } // 3. 检查时间戳(防重放攻击,允许 60 秒误差) if (abs(time() - $timestamp) > 60) { -// throw new HttpException(401, '请求已超时'); + return response('请求已超时', 401); } // 4. 检查随机字符串 $cacheKey = "api_nonce:" . $nonce; if (Cache::has($cacheKey)) { -// throw new HttpException(401, '重复请求'); + return response('重复请求', 401); } Cache::setex($cacheKey, 60, 1); // 5. 验证签名 if (!$this->checkSignature($params, $timestamp, $nonce, $signature)) { - throw new HttpException(401, '签名验证失败'); + return response('签名验证失败', 401); } return $next($request); @@ -61,7 +59,7 @@ class ApiAuth protected function checkSignature($params, $timestamp, $nonce, $signature) { - $appSecret = $this->apps['appSecret']; + $appSecret = $this->apps['app_secret']; unset($params['/']); ksort($params); $stringToBeSigned = http_build_query($params) . '&app_secret=' . $appSecret . '×tamp=' . $timestamp . '&nonce=' . $nonce; diff --git a/app/common/service/JK8Services.php b/app/common/service/JK8Services.php index cb21f59..976f710 100644 --- a/app/common/service/JK8Services.php +++ b/app/common/service/JK8Services.php @@ -1,21 +1,21 @@ config = config('jk8'); - $this->domain = config('jk8.domain'); - $this->accessId = config('jk8.access_id'); - $this->token = config('jk8.token'); + $config = ConfigModel::where('group', 'jdk_api')->column('value', 'name'); + $this->domain = $config['api_url']; + $this->accessId = $config['access_id']; + $this->token = $config['token']; } /** @@ -111,96 +111,4 @@ class Jk8Services return $result['data']['transactionId']; } - /** - * 存款 - * @param array $params 参数 - * @return array - * @throws Exception - * @throws GameException - */ - public function deposit(array $params): array - { - $auth = $this->auth($params); - if (!$auth['status']) { - throw new GameException($auth['message']); - } - $data = [ - 'username' => $params['name'], - 'auth' => $auth['auth'], - 'amount' => $params['amount'], - 'currency' => 'MYR', - 'orderid' => $params['orderNo'], - 'redirect_url' => $this->returnUrl . '?orderNo='.$params['orderNo'], - ]; - return doFormCurl($this->domain . '/merchant/generate_orders',$data); - } - - /** - * 代付 - * @param array $params 参数 - * @throws GameException|Exception - */ - public function payout(array $params): array - { - $params['type'] = 'online'; - $auth = $this->auth($params); - if (!$auth['status']) { - throw new GameException($auth['message']); - } - $data = [ - 'auth' => $auth['auth'], - 'amount' => $params['amount'], - 'currency' => 'MYR', - 'orderid' => $params['orderNo'], - 'bank_id' => $params['bankCode'], - 'holder_name' => $params['bankAccountName'], - 'account_no' => $params['bankAccountNo'], - ]; - return doFormCurl($this->domain . '/merchant/withdraw_orders',$data); - } - - /** - * 验证 - * @param array $params 参数 - * @throws Exception - */ - public function depositVerifySign(array $params) - { - $orderInfo = PlayerRechargeRecord::query() - ->where(['tradeno' => $params['order_id']]) - ->first(); - if (empty($orderInfo)) { - return false; - } - return MD5($this->config[$orderInfo['payment_method']]['secret_key'] . $params['order_id']); - } - - /** - * 验证 - * @param array $params 参数 - * @throws Exception - */ - public function withdrawalVerifySign(array $params) - { - return MD5($this->config['online']['secret_key'] . $params['order_id']); - } - - /** - * 订单查询 - * @param $orderNo - * @return array - * @throws Exception - */ - public function query($orderNo): array - { - $orderInfo = PlayerRechargeRecord::query() - ->where(['tradeno' => $orderNo]) - ->first(); - $data = [ - 'username' => $this->config[$orderInfo['payment_method']]['merchantId'], - 'id' => $orderNo, - ]; - return doFormCurl($this->domain . '/merchant/check_status',$data); - } - } diff --git a/app/common/service/XyxServices.php b/app/common/service/XyxServices.php new file mode 100644 index 0000000..05b331e --- /dev/null +++ b/app/common/service/XyxServices.php @@ -0,0 +1,308 @@ + 'Casino', + 'Fishing' => 'Fishing', + 'Bingo' => 'Bingo', + 'Slot' => 'Slot', + ]; + + public $localGameType = [ + 'slot' => '2', + 'multiplayer' => '1', + 'fishing' => '7', + 'bet' => '5', + ]; + public $failCode = [ + '0' => '应用不存在', + '1000' => '参数错误', + '1101' => '玩家账号', + '1102' => '密码', + '1103' => '玩家id', + '1204' => '金额', + '1205' => '开始时间', + '1206' => '结束时间', + '2001' => '渠道不存在', + '2100' => '用户错误', + '2102' => '用户已注册', + '2103' => '玩家不存在', + '2104' => '玩家已被停用', + '2205' => '钱包余额不足', + '2206' => '结束时间必须大于起始时间', + '2207' => '查询时间不能超过1小时', + '3000' => '系统错误', + '3201' => '钱包转出失败', + '3202' => '钱包转入失败', + '3902' => 'JWT清除失败', + ]; + + public function __construct($player = null) + { + $config = ConfigModel::where('group', 'mini_games')->column('value', 'name'); + $this->appId = $config['app_id']; + $this->appSecret = $config['app_secret']; + $this->domain = $config['domain']; + if (!empty($player)) { + $this->player = $player; + $this->account = $player['xyx_account']; + $this->password = $player['xyx_password']; + if (empty($this->account) || empty($this->password)) { + $this->createPlayer($player); + } + } + } + + /** + * 生成请求 + * @param $url + * @param array $params + * @return array|mixed + * @throws \Exception + */ + public function doXyxCurl($url, array $params = []) + { + $headers = [ + 'appId' => $this->appId, + 'timestamp' => time(), + 'nonceStr' => Str::random(6), + ]; + $data = array_merge($params,$headers); + ksort($data); + $signature = hash('sha256', urldecode(http_build_query($data)) . $this->appSecret); + $headers['signature'] = $signature; + + $client = new Client([ + 'timeout' => 7, + 'verify' => true, + ]); + try { + $response = $client->post($url, [ + 'json' => $params, // 相当于 asJson(),会自动设置 Content-Type: application/json + 'headers' => $headers, + ]); + + if ($response->getStatusCode() !== 200) { + throw new \Exception(lang('system_busy')); // ThinkPHP 使用 lang() 代替 trans() + } + + $contents = $response->getBody()->getContents(); + $data = json_decode($contents, true); + + if (empty($data)) { + throw new \Exception(lang('system_busy')); + } + + return $data; + + } catch (GuzzleException $e) { + // 请求失败(如超时、证书错误等) + throw new \Exception(lang('system_busy')); + } + } + + /** + * 获取token + */ + public function getToken($gameId) + { + $params = [ + 'account' => $this->account, + 'password' => $this->password, + 'game_id' => $gameId, + ]; + $res = $this->doXyxCurl($this->domain . 'agent/api/get-access-token', $params); + if ($res['code'] != 200) { + throw new \Exception($this->failCode[$res['code']]); + } + return $res['data']['access_token']; + } + + /** + * 创建玩家 + */ + public function createPlayer($player) + { + $params = [ + 'account' => 'jk' . $player['jk_user_id'], + 'password' => Str::random(12), + ]; + $res = $this->doXyxCurl($this->domain . 'agent/api/create-player', $params); + if ($res['code'] != 200) { + throw new \Exception($this->failCode[$res['code']]); + } + $player->xyx_account = $params['account']; + $player->xyx_password = $params['password']; + $player->save(); + $this->account = $params['account']; + $this->password = $params['password']; + return $res; + } + + /** + * 玩家进入游戏 + * @param array $data + * @return string + */ + public function login(array $data = []): string + { + $token = $this->getToken($data['gameCode']); + $out = UserScoreLog::where('user_id', $this->player->id) + ->where('type', 2) + ->whereNotNull('created_by') + ->order('id', 'desc') + ->find(); + if ($out) { + $this->balanceTransferIn($out->game_type); + } + $this->balanceTransferOut($data['gameCode']); + $headers = [ + 'appId' => $this->appId, + 'timestamp' => time(), + 'nonceStr' => Str::random(6), + ]; + ksort($headers); + $signature = hash('sha256', urldecode(http_build_query($headers)) . $this->appSecret); + $headers['signature'] = $signature; + $headers['Authorization'] = 'Bearer ' . $token; + + $client = new Client([ + 'timeout' => 7, + 'allow_redirects' => false, + ]); + + try { + $response = $client->request('GET', $this->domain . 'agent/enter-game', [ + 'headers' => $headers, + ]); + if ($response->getStatusCode() == 302) { + return $response->getHeader('Location')[0] ?? ''; + } + } catch (RequestException $e) { + if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 302) { + return $e->getResponse()->getHeader('Location')[0] ?? ''; + } + } + + return false; + } + + /** + * 获取玩家游戏平台余额 + */ + public function getBalance() + { + $params = [ + 'account' => $this->account, + ]; + $res = $this->doXyxCurl($this->domain . 'agent/api/get-player-info', $params); + if ($res['code'] != 200) { + throw new \Exception($this->failCode[$res['code']]); + } + return $res['data']['point']; + } + + /** + * 玩家钱包转入游戏平台 + */ + public function balanceTransferOut($gameType) + { + $score = $this->player->userScore()->where('game_type', $gameType)->value('score'); + if ($score == 0) { + return true; + } + return $this->setBalanceTransfer(2, $gameType, $score); + } + + /** + * 游戏平台转入玩家钱包 + */ + public function balanceTransferIn($gameType) + { + $balance = $this->getBalance(); + if ($balance == 0) { + $userScore = $this->player->userScore()->where('game_type', $gameType)->find(); + $userScoreLog = new UserScoreLog(); + $userScoreLog->save([ + 'user_id' => $this->player->id, + 'game_type' => $gameType, + 'before' => $userScore->score, + 'after' => $userScore->score, + 'score' => 0, + 'memo' => '转入', + 'type' => 1, + 'created_by'=> 0, + ]); + return true; + } + return $this->setBalanceTransfer(1, $gameType, $balance); + } + + /** + * 轉帳進出額度 + * @param $type + * @param float $amount + * @return array|mixed|null + * @throws \Exception + */ + protected function setBalanceTransfer($type,$gameType, float $amount = 0) + { + $yCode = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; + $tradeno = $yCode[intval(date('Y')) - 2011] . strtoupper(dechex(date('m'))) . date('d') . substr(microtime(), 2, 5) . sprintf('%02d', rand(0, 99)); + $params = [ + 'account' => $this->account, + 'point' => $amount, + 'tradeno' => $tradeno + ]; + if ($type == 2) { + $res = $this->doXyxCurl($this->domain . 'agent/api/transfer-in', $params); + } else { + $res = $this->doXyxCurl($this->domain . 'agent/api/transfer-out', $params); + } + if ($res['code'] != 200) { + throw new \Exception($this->failCode[$res['code']]); + } + $userScore = $this->player->userScore()->where('game_type', $gameType)->find(); + $userScore->created_by = 0; + if ($type == 2) { + $userScore->score = 0; + $userScore->memo = '转出'; + } else { + $userScore->score += $amount; + $userScore->memo = '转入'; + } + $userScore->save(); + return $res; + } + + public function getReportRecord(string $startTime = '', string $endTime = '') + { + $params = [ + 'start_date' => $startTime, + 'end_date' => $endTime, + ]; + $res = $this->doXyxCurl($this->domain . 'agent/api/draw-records-by-date', $params); + if ($res['code'] != 200) { + throw new \Exception($this->failCode[$res['code']]); + } + return $res['data']; + } +} diff --git a/web/src/views/backend/routine/config/index.vue b/web/src/views/backend/routine/config/index.vue index f3452f3..389b4c4 100644 --- a/web/src/views/backend/routine/config/index.vue +++ b/web/src/views/backend/routine/config/index.vue @@ -94,13 +94,6 @@ - - - -
{{ item['key'] }}
-
-
-