Files
dafuweng-saiadmin6.x/server/app/api/controller/v1/GameController.php
zhenhui 1f25280dfd 1.所有接口需要根据agent_id绑定渠道
2.移除所有记录页面的更新按钮,只能查看数据
3.将所有软删除修改为硬删除
2026-05-19 12:04:34 +08:00

521 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\api\controller\v1;
use app\api\logic\UserLogic;
use app\api\util\ReturnCode;
use app\dice\model\game\DiceGame;
use app\dice\model\player\DicePlayer;
use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
use support\think\Db;
use app\api\controller\BaseController;
use support\Request;
use support\Response;
use app\api\cache\UserCache;
/**
* 平台 v1 游戏接口
* 请求头auth-token
*/
class GameController extends BaseController
{
/** 游戏记录 / 钱包流水 / 中奖券记录等拉取类接口的条数上限(与对接文档一致) */
private const PLAYER_PULL_RECORD_MAX_LIMIT = 2000;
private const PLAYER_PULL_RECORD_DEFAULT_LIMIT = 20;
/** 拉取类流水仅允许查询「当前时间起向前」的最大天数(含起止) */
private const PLAYER_PULL_RECORD_MAX_RANGE_DAYS = 7;
private const GAME_PUBLIC_FIELDS = [
'provider',
'provider_code',
'game_code',
'game_key',
'game_type',
'logo',
'game_url',
'hall_url',
'status',
'sort',
];
/** getPlayerInfo 仅查询需返回的列,减轻 IO敏感/内部字段不入库查询) */
private const PLAYER_INFO_DB_FIELDS = [
'id', 'username', 'phone', 'uid', 'name', 'status', 'coin', 'is_up', 'admin_id',
'total_ticket_count', 'paid_ticket_count', 'free_ticket_count', 'free_ticket',
'total_win_coin',
'create_time', 'update_time',
];
/**
* 获取游戏列表
* POST 参数lang可选zh/en默认 zh
* 当前返回启用中的游戏列表
*/
public function getGameList(Request $request): Response
{
$lang = $this->resolveLang($request->post('lang', 'zh'));
$games = $this->buildPublicGameList($lang, $this->agentDeptId($request));
return $this->success([
'game_list' => $games,
]);
}
/**
* 获取游戏大厅信息(脱敏)
* POST 参数lang可选zh/en默认 zh
*/
public function getGameHall(Request $request): Response
{
$lang = $this->resolveLang($request->post('lang', 'zh'));
$games = $this->buildPublicGameList($lang, $this->agentDeptId($request));
$hallUrl = '';
if (!empty($games)) {
$hallUrl = $games[0]['hall_url'] ?? '';
}
return $this->success([
'provider' => 'Dicey Fun',
'provider_code' => 'DF',
'hall_url' => $hallUrl,
'game_list' => $games,
]);
}
/**
* 获取游戏地址
* 根据 username 创建登录 tokenJWT拼接游戏地址返回
*/
public function getGameUrl(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$password = trim((string) ($request->post('password', '123456')));
$time = trim((string) ($request->post('time', '')));
if ($username === '') {
return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
}
if ($password === '') {
$password = '123456';
}
if ($time === '') {
$time = (string) time();
}
$deptId = $this->agentDeptId($request);
$adminId = $this->agentAdminId($request);
$adminIdsInTopDept = UserLogic::getAdminIdsByAgentIdTopDept(trim((string) ($request->agent_id ?? '')));
$lang = trim((string) ($request->post('lang', 'zh')));
$lang = in_array($lang, ['en', 'zh'], true) ? $lang : 'zh';
try {
$logic = new UserLogic();
$result = $logic->loginByUsername($username, $password, $lang, 0.0, $time, $adminId, $adminIdsInTopDept, $deptId);
} catch (\plugin\saiadmin\exception\ApiException $e) {
return $this->fail($e->getMessage(), ReturnCode::PARAMS_ERROR);
}
$gameUrlBase = rtrim(config('api.game_url', 'dice-game.h55555game.top'), '/');
$tokenInUrl = str_replace('%3D', '=', urlencode($result['token']));
$url = $gameUrlBase . '/?token=' . $tokenInUrl . '&lang=' . $lang;
return $this->success([
'url' => $url,
]);
}
/**
* 获取用户信息
* 参数usernamePOST JSON / 表单 / Query 均可input 合并读取降低偶发空参)
* 返回 DicePlayer 中非敏感信息;短期 Redis 快照 + 窄字段查询降低延迟
*/
public function getPlayerInfo(Request $request): Response
{
$usernameRaw = $request->input('username', '');
$username = is_string($usernameRaw) ? trim($usernameRaw) : '';
$deptId = $this->agentDeptId($request);
if ($username === '') {
return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
}
$cached = UserCache::getPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username));
if ($cached !== null) {
return $this->success($cached);
}
$player = DicePlayer::field(self::PLAYER_INFO_DB_FIELDS)->where('username', $username)->where('dept_id', $deptId)->find();
if (!$player) {
return $this->fail('User not found', ReturnCode::PARAMS_ERROR);
}
$hidden = ['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight', 'delete_time'];
$info = $player->hidden($hidden)->toArray();
UserCache::setPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username), $info);
return $this->success($info);
}
/**
* 解析拉取类流水接口的 limit与 getPlayerGameRecord / getPlayerWalletRecord / getPlayerTicketRecord 共用)
*/
private function resolvePullRecordLimit(Request $request): int
{
$limit = (int) $request->post('limit', self::PLAYER_PULL_RECORD_DEFAULT_LIMIT);
if ($limit < 1 || $limit > self::PLAYER_PULL_RECORD_MAX_LIMIT) {
$limit = self::PLAYER_PULL_RECORD_DEFAULT_LIMIT;
}
return $limit;
}
/**
* 将 start_create_time / end_create_time 规范为「最近 7 天内」且跨度不超过 7 天的闭区间。
*
* @return array{ok: true, start: string, end: string}|array{ok: false, message: string}
*/
private function resolvePullRecordTimeWindow(string $startRaw, string $endRaw): array
{
$nowTs = time();
$weekAgoTs = strtotime('-' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $nowTs);
if ($weekAgoTs === false) {
$weekAgoTs = $nowTs - self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
}
$nowStr = date('Y-m-d H:i:s', $nowTs);
$weekAgoStr = date('Y-m-d H:i:s', $weekAgoTs);
if ($startRaw === '' && $endRaw === '') {
return ['ok' => true, 'start' => $weekAgoStr, 'end' => $nowStr];
}
$startTs = $startRaw === '' ? null : strtotime($startRaw);
$endTs = $endRaw === '' ? null : strtotime($endRaw);
if ($startRaw !== '' && $startTs === false) {
return ['ok' => false, 'message' => 'Invalid start_create_time'];
}
if ($endRaw !== '' && $endTs === false) {
return ['ok' => false, 'message' => 'Invalid end_create_time'];
}
if ($startRaw === '' && $endRaw !== '' && $endTs !== null) {
$endTs = min($endTs, $nowTs);
if ($endTs < $weekAgoTs) {
return ['ok' => false, 'message' => 'end_create_time must be within the last 7 days'];
}
$startTs = strtotime('-' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $endTs);
if ($startTs === false) {
$startTs = $endTs - self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
}
if ($startTs < $weekAgoTs) {
$startTs = $weekAgoTs;
}
return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)];
}
if ($startRaw !== '' && $startTs !== null && $endRaw === '') {
if ($startTs > $nowTs) {
return ['ok' => false, 'message' => 'start_create_time cannot be in the future'];
}
if ($startTs < $weekAgoTs) {
return ['ok' => false, 'message' => 'start_create_time cannot be earlier than 7 days ago'];
}
$endTs = strtotime('+' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $startTs);
if ($endTs === false) {
$endTs = $startTs + self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
}
$endTs = min($endTs, $nowTs);
return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)];
}
if ($startTs !== null && $endTs !== null) {
if ($endTs > $nowTs) {
$endTs = $nowTs;
}
if ($startTs > $endTs) {
return ['ok' => false, 'message' => 'start_create_time cannot be after end_create_time'];
}
if ($startTs < $weekAgoTs) {
return ['ok' => false, 'message' => 'start_create_time cannot be earlier than 7 days ago'];
}
if ($endTs < $weekAgoTs) {
return ['ok' => false, 'message' => 'end_create_time must be within the last 7 days'];
}
$maxEndForStart = strtotime('+' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $startTs);
if ($maxEndForStart === false) {
$maxEndForStart = $startTs + self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
}
if ($endTs > $maxEndForStart) {
return ['ok' => false, 'message' => 'Time range cannot exceed 7 days'];
}
return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)];
}
return ['ok' => false, 'message' => 'Invalid time parameters'];
}
/**
* 获取游戏记录
* POST 参数username非必填, start_create_time, end_create_time可选须在「最近 7 天」内且跨度不超过 7 天;均不传则默认最近 7 天), limit非必填返回条数上限
* 返回 DicePlayRecord 中非敏感信息
*/
public function getPlayerGameRecord(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$startCreateTime = trim((string) ($request->post('start_create_time', '')));
$endCreateTime = trim((string) ($request->post('end_create_time', '')));
$window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
if (!$window['ok']) {
return $this->fail($window['message'], ReturnCode::PARAMS_ERROR);
}
$limit = $this->resolvePullRecordLimit($request);
$query = DicePlayRecord::where('dept_id', $deptId)->order('id', 'desc');
if ($username !== '') {
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->success([]);
}
$query->where('player_id', (int) $player->id);
}
$query->where('create_time', '>=', $window['start']);
$query->where('create_time', '<=', $window['end']);
$list = $query->limit($limit)->select()->toArray();
$playerIds = array_unique(array_column($list, 'player_id'));
if (!empty($playerIds)) {
$players = DicePlayer::whereIn('id', $playerIds)->where('dept_id', $deptId)->field('id,username,phone')->select()->toArray();
$playerMap = [];
foreach ($players as $p) {
$playerMap[(int) ($p['id'] ?? 0)] = $p;
}
foreach ($list as &$item) {
$item['dice_player'] = $playerMap[(int) ($item['player_id'] ?? 0)] ?? null;
}
}
return $this->success($list);
}
/**
* 获取钱包流水
* POST 参数username非必填, start_create_time, end_create_time可选规则同 getPlayerGameRecord, limit非必填
* 返回 DicePlayerWalletRecord 中非敏感信息
*/
public function getPlayerWalletRecord(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$startCreateTime = trim((string) ($request->post('start_create_time', '')));
$endCreateTime = trim((string) ($request->post('end_create_time', '')));
$window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
if (!$window['ok']) {
return $this->fail($window['message'], ReturnCode::PARAMS_ERROR);
}
$limit = $this->resolvePullRecordLimit($request);
$query = DicePlayerWalletRecord::where('dept_id', $deptId)->order('id', 'desc');
if ($username !== '') {
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->success([]);
}
$query->where('player_id', (int) $player->id);
}
$query->where('create_time', '>=', $window['start']);
$query->where('create_time', '<=', $window['end']);
$list = $query->with(['dicePlayer' => function ($q) {
$q->field('id,username,phone');
}])->limit($limit)->select()->toArray();
return $this->success($list);
}
/**
* 获取用户中奖券获取记录
* POST 参数username非必填, start_create_time, end_create_time可选规则同 getPlayerGameRecord, limit非必填
* 返回 DicePlayerTicketRecord 中非敏感信息
*/
public function getPlayerTicketRecord(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$startCreateTime = trim((string) ($request->post('start_create_time', '')));
$endCreateTime = trim((string) ($request->post('end_create_time', '')));
$window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
if (!$window['ok']) {
return $this->fail($window['message'], ReturnCode::PARAMS_ERROR);
}
$limit = $this->resolvePullRecordLimit($request);
$query = DicePlayerTicketRecord::where('dept_id', $deptId)->order('id', 'desc');
if ($username !== '') {
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->success([]);
}
$query->where('player_id', (int) $player->id);
}
$query->where('create_time', '>=', $window['start']);
$query->where('create_time', '<=', $window['end']);
$list = $query->with(['dicePlayer' => function ($q) {
$q->field('id,username,phone');
}])->limit($limit)->select()->toArray();
return $this->success($list);
}
/**
* 钱包转入转出
* POST 参数username必填, coin转入>0 或 转出<0
* 创建 DicePlayerWalletRecordtype: 0=充值(coin>0), 1=提现(coin<0)
* 返回创建的记录
*/
public function setPlayerWallet(Request $request): Response
{
$username = trim((string) ($request->post('username', '')));
$deptId = $this->agentDeptId($request);
$coin = $request->post('coin');
if ($username === '') {
return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
}
if ($coin === null || $coin === '') {
return $this->fail('coin is required', ReturnCode::PARAMS_ERROR);
}
$coinVal = (float) $coin;
if ($coinVal === 0.0) {
return $this->fail('coin cannot be 0', ReturnCode::PARAMS_ERROR);
}
$player = $this->findPlayerByUsername($username, $deptId);
if (!$player) {
return $this->fail('User not found', ReturnCode::PARAMS_ERROR);
}
$walletBefore = (float) ($player->coin ?? 0);
$walletAfter = $walletBefore + $coinVal;
if ($coinVal < 0 && $walletBefore < -$coinVal) {
return $this->fail('Insufficient balance to transfer', ReturnCode::BUSINESS_ERROR);
}
$type = $coinVal > 0 ? 0 : 1;
$remark = $coinVal > 0 ? '充值' : '提现';
try {
Db::startTrans();
$player->coin = $walletAfter;
$player->save();
$adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
$record = DicePlayerWalletRecord::create([
'dept_id' => $deptId,
'player_id' => (int) $player->id,
'admin_id' => $adminId,
'coin' => $coinVal,
'type' => $type,
'wallet_before' => $walletBefore,
'wallet_after' => $walletAfter,
'total_ticket_count' => 0,
'paid_ticket_count' => 0,
'free_ticket_count' => 0,
'remark' => $remark,
'user_id' => 0,
]);
Db::commit();
} catch (\Throwable $e) {
Db::rollback();
return $this->fail('Operation failed: ' . $e->getMessage(), ReturnCode::SERVER_ERROR);
}
// 出于安全:删除该玩家相关缓存,后续 API 调用按需重建
UserCache::deleteUser($player->id);
if ($player->username !== '') {
UserCache::deletePlayerByUsername($player->username);
UserCache::deletePlayerByUsername($this->scopedUsername($deptId, (string) $player->username));
}
$recordArr = $record->toArray();
$recordArr['dice_player'] = ['id' => (int) $player->id, 'username' => $player->username ?? '', 'phone' => $player->phone ?? ''];
return $this->success($recordArr);
}
private function resolveLang($lang): string
{
if (!is_string($lang)) {
return 'zh';
}
$langValue = strtolower(trim($lang));
if (!in_array($langValue, ['zh', 'en'], true)) {
return 'zh';
}
return $langValue;
}
private function buildPublicGameList(string $lang, int $deptId): array
{
$rows = DiceGame::where('status', 1)
->where('dept_id', $deptId)
->order('sort', 'asc')
->order('id', 'asc')
->field(array_merge(self::GAME_PUBLIC_FIELDS, ['game_name', 'game_name_en']))
->select()
->toArray();
if (empty($rows)) {
return [];
}
$games = [];
foreach ($rows as $row) {
$game = [];
foreach (self::GAME_PUBLIC_FIELDS as $fieldName) {
$game[$fieldName] = $row[$fieldName] ?? '';
}
$gameNameEn = $row['game_name_en'] ?? '';
$game['game_name'] = $lang === 'en' && $gameNameEn !== '' ? $gameNameEn : ($row['game_name'] ?? '');
$games[] = $game;
}
return $games;
}
private function agentDeptId(Request $request): int
{
return (int) ($request->agent_dept_id ?? 0);
}
private function agentAdminId(Request $request): ?int
{
$adminId = (int) ($request->agent_admin_id ?? 0);
return $adminId > 0 ? $adminId : null;
}
private function scopedUsername(int $deptId, string $username): string
{
return $deptId . ':' . $username;
}
private function findPlayerByUsername(string $username, int $deptId): ?DicePlayer
{
$player = DicePlayer::where('username', $username)->where('dept_id', $deptId)->find();
return $player ?: null;
}
}