From 064ba727e055470bed333d9b8bb547c2c11af171 Mon Sep 17 00:00:00 2001
From: zhouzp <1558048883@qq.com>
Date: Thu, 11 Jun 2026 13:49:20 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=BB=9F=E8=AE=A1=EF=BC=8C?=
=?UTF-8?q?=E5=B0=8F=E6=B8=B8=E6=88=8F=EF=BC=8C=E7=B3=BB=E7=BB=9F=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/admin/controller/user/MoneyLog.php | 283 ++++++++++++++++
app/admin/controller/user/User.php | 98 +++++-
app/admin/model/PromoReward.php | 32 ++
app/admin/model/UserScore.php | 11 +-
app/admin/model/UserScoreLog.php | 11 +-
app/api/controller/External.php | 99 +++++-
app/api/route/app.php | 2 +-
app/common/middleware/ApiAuth.php | 18 +-
app/common/service/JK8Services.php | 102 +-----
app/common/service/XyxServices.php | 308 ++++++++++++++++++
.../views/backend/routine/config/index.vue | 7 -
11 files changed, 827 insertions(+), 144 deletions(-)
create mode 100644 app/admin/model/PromoReward.php
create mode 100644 app/common/service/XyxServices.php
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'] }}
-
-
-