API接口-初版
This commit is contained in:
@@ -21,3 +21,6 @@ DATABASE_PREFIX =
|
|||||||
|
|
||||||
# 缓存(config/cache.php)
|
# 缓存(config/cache.php)
|
||||||
CACHE_DRIVER = file
|
CACHE_DRIVER = file
|
||||||
|
|
||||||
|
# 移动端接口鉴权(/api/v1/authToken)
|
||||||
|
AUTH_TOKEN_SECRET = 564d14asdasd113e46542asd6das1a2a
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use app\common\facade\Token;
|
|||||||
use app\common\model\UserScoreLog;
|
use app\common\model\UserScoreLog;
|
||||||
use app\common\model\UserMoneyLog;
|
use app\common\model\UserMoneyLog;
|
||||||
use app\common\controller\Frontend;
|
use app\common\controller\Frontend;
|
||||||
|
use app\common\facade\Token as TokenFacade;
|
||||||
use support\validation\Validator;
|
use support\validation\Validator;
|
||||||
use support\validation\ValidationException;
|
use support\validation\ValidationException;
|
||||||
use Webman\Http\Request;
|
use Webman\Http\Request;
|
||||||
@@ -20,6 +21,51 @@ class Account extends Frontend
|
|||||||
protected array $noNeedLogin = ['retrievePassword'];
|
protected array $noNeedLogin = ['retrievePassword'];
|
||||||
protected array $noNeedPermission = ['verification', 'changeBind'];
|
protected array $noNeedPermission = ['verification', 'changeBind'];
|
||||||
|
|
||||||
|
public function userProfile(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeFrontend($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$authToken = trim((string) $request->header('auth-token', ''));
|
||||||
|
if ($authToken === '') {
|
||||||
|
return $this->mobileResult(1101, 'Missing auth-token');
|
||||||
|
}
|
||||||
|
$tokenData = TokenFacade::get($authToken);
|
||||||
|
$type = $tokenData['type'] ?? '';
|
||||||
|
$expireTime = $tokenData['expire_time'] ?? 0;
|
||||||
|
if ($type !== 'auth-token' || !is_numeric($expireTime) || $expireTime < time()) {
|
||||||
|
return $this->mobileResult(1101, 'auth-token is invalid or expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->auth->getUser();
|
||||||
|
$payload = [
|
||||||
|
'code' => 1,
|
||||||
|
'message' => __('ok'),
|
||||||
|
'data' => [
|
||||||
|
'id' => $user->id,
|
||||||
|
'username' => $user->username,
|
||||||
|
'head_image' => $user->avatar ?? '',
|
||||||
|
'coin' => $user->coin,
|
||||||
|
'current_streak' => $user->current_streak ?? 0,
|
||||||
|
'channel_id' => $user->channel_id,
|
||||||
|
'risk_flags' => $user->risk_flags ?? 0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
return \response(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 200, ['Content-Type' => 'application/json']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mobileResult(int $code, string $message, array $data = []): Response
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'code' => $code,
|
||||||
|
'message' => __($message),
|
||||||
|
'data' => $data,
|
||||||
|
];
|
||||||
|
return \response(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 200, ['Content-Type' => 'application/json']);
|
||||||
|
}
|
||||||
|
|
||||||
public function overview(Request $request): Response
|
public function overview(Request $request): Response
|
||||||
{
|
{
|
||||||
$response = $this->initializeFrontend($request);
|
$response = $this->initializeFrontend($request);
|
||||||
|
|||||||
138
app/api/controller/Auth.php
Normal file
138
app/api/controller/Auth.php
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\facade\Token;
|
||||||
|
use app\common\library\Auth as UserAuth;
|
||||||
|
use app\common\model\User;
|
||||||
|
use ba\Random;
|
||||||
|
use support\think\Db;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use support\Response;
|
||||||
|
|
||||||
|
class Auth extends MobileBase
|
||||||
|
{
|
||||||
|
protected array $noNeedLogin = ['userRegister', 'userLogin', 'tokenRefresh'];
|
||||||
|
|
||||||
|
public function userRegister(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = trim((string) $request->post('account', ''));
|
||||||
|
$accountType = trim((string) $request->post('account_type', ''));
|
||||||
|
$password = (string) $request->post('password', '');
|
||||||
|
$inviteCode = trim((string) $request->post('invite_code', ''));
|
||||||
|
|
||||||
|
if ($account === '' || $accountType === '' || $password === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
if ($accountType !== 'phone' && $accountType !== 'email') {
|
||||||
|
return $this->mobileError(1003, 'Invalid parameter value');
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = $account;
|
||||||
|
$mobile = '';
|
||||||
|
$email = '';
|
||||||
|
if ($accountType === 'phone') {
|
||||||
|
$mobile = $account;
|
||||||
|
}
|
||||||
|
if ($accountType === 'email') {
|
||||||
|
$email = $account;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extend = [];
|
||||||
|
if ($inviteCode !== '') {
|
||||||
|
$inviterAdmin = Db::name('admin')->field(['id', 'channel_id'])->where('invite_code', $inviteCode)->find();
|
||||||
|
if (!$inviterAdmin) {
|
||||||
|
return $this->mobileError(2002, 'Invite code does not exist');
|
||||||
|
}
|
||||||
|
$extend['register_invite_code'] = $inviteCode;
|
||||||
|
$extend['admin_id'] = $inviterAdmin['id'];
|
||||||
|
$extend['channel_id'] = $inviterAdmin['channel_id'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$registered = $this->auth->register($username, $password, $mobile, $email, 1, $extend);
|
||||||
|
if (!$registered) {
|
||||||
|
return $this->mobileError(2000, (string) $this->auth->getError());
|
||||||
|
}
|
||||||
|
|
||||||
|
$loggedIn = $this->auth->login($username, $password, true);
|
||||||
|
if (!$loggedIn) {
|
||||||
|
return $this->mobileError(2000, 'Registered successfully but login failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userInfo = $this->auth->getUserInfo();
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'user_id' => $userInfo['id'] ?? null,
|
||||||
|
'access_token' => $userInfo['token'] ?? '',
|
||||||
|
'expires_in' => config('buildadmin.user_token_keep_time', 259200),
|
||||||
|
'profile' => [
|
||||||
|
'username' => $userInfo['username'] ?? '',
|
||||||
|
'coin' => $userInfo['coin'] ?? '0.0000',
|
||||||
|
'channel_id' => $userInfo['channel_id'] ?? null,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function userLogin(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$account = trim((string) $request->post('account', ''));
|
||||||
|
$password = (string) $request->post('password', '');
|
||||||
|
if ($account === '' || $password === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ok = $this->auth->login($account, $password, true);
|
||||||
|
if (!$ok) {
|
||||||
|
return $this->mobileError(1101, 'Incorrect account or password');
|
||||||
|
}
|
||||||
|
$userInfo = $this->auth->getUserInfo();
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'access_token' => $userInfo['token'] ?? '',
|
||||||
|
'refresh_token' => $userInfo['refresh_token'] ?? '',
|
||||||
|
'expires_in' => config('buildadmin.user_token_keep_time', 259200),
|
||||||
|
'user' => [
|
||||||
|
'id' => $userInfo['id'] ?? null,
|
||||||
|
'username' => $userInfo['username'] ?? '',
|
||||||
|
'coin' => $userInfo['coin'] ?? '0.0000',
|
||||||
|
'risk_flags' => $userInfo['risk_flags'] ?? 0,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tokenRefresh(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$refreshToken = trim((string) $request->post('refresh_token', ''));
|
||||||
|
if ($refreshToken === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokenData = Token::get($refreshToken);
|
||||||
|
if (!$tokenData || $tokenData['type'] !== UserAuth::TOKEN_TYPE . '-refresh' || $tokenData['expire_time'] < time()) {
|
||||||
|
return $this->mobileError(1101, 'Login status has expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
$newToken = Random::uuid();
|
||||||
|
Token::set($newToken, UserAuth::TOKEN_TYPE, $tokenData['user_id'], config('buildadmin.user_token_keep_time', 259200));
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'access_token' => $newToken,
|
||||||
|
'expires_in' => config('buildadmin.user_token_keep_time', 259200),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
173
app/api/controller/Finance.php
Normal file
173
app/api/controller/Finance.php
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\model\DepositOrder;
|
||||||
|
use app\common\model\WithdrawOrder;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use support\Response;
|
||||||
|
|
||||||
|
class Finance extends MobileBase
|
||||||
|
{
|
||||||
|
public function depositCreate(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$payAmountFiat = (string) $request->post('pay_amount_fiat', '');
|
||||||
|
$fiatCurrency = trim((string) $request->post('fiat_currency', ''));
|
||||||
|
$channel = trim((string) $request->post('channel', ''));
|
||||||
|
$idempotencyKey = trim((string) $request->post('idempotency_key', ''));
|
||||||
|
if ($payAmountFiat === '' || $fiatCurrency === '' || $channel === '' || $idempotencyKey === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderNo = 'DP' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
|
||||||
|
$coinAmount = $payAmountFiat;
|
||||||
|
DepositOrder::create([
|
||||||
|
'order_no' => $orderNo,
|
||||||
|
'user_id' => $this->auth->id,
|
||||||
|
'fiat_currency' => $fiatCurrency,
|
||||||
|
'fiat_amount' => $payAmountFiat,
|
||||||
|
'fx_rate' => '1.00000000',
|
||||||
|
'coin_amount' => $coinAmount,
|
||||||
|
'gateway' => $channel,
|
||||||
|
'status' => 0,
|
||||||
|
'create_time' => time(),
|
||||||
|
'update_time' => time(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'order_no' => $orderNo,
|
||||||
|
'coin_amount' => $coinAmount,
|
||||||
|
'pay_url' => '',
|
||||||
|
'status' => 'pending',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function depositDetail(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$orderNo = trim((string) $request->get('order_no', ''));
|
||||||
|
if ($orderNo === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
$order = DepositOrder::where('order_no', $orderNo)->where('user_id', $this->auth->id)->find();
|
||||||
|
if (!$order) {
|
||||||
|
return $this->mobileError(2003, 'Order does not exist');
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'order_no' => $order->order_no,
|
||||||
|
'status' => $this->mapDepositStatus($order->status),
|
||||||
|
'coin_amount' => $order->coin_amount,
|
||||||
|
'create_time' => $order->create_time,
|
||||||
|
'finish_time' => $order->paid_at,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withdrawCreate(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$withdrawCoin = (string) $request->post('withdraw_coin', '');
|
||||||
|
$receiveAccount = trim((string) $request->post('receive_account', ''));
|
||||||
|
$receiveType = trim((string) $request->post('receive_type', ''));
|
||||||
|
$idempotencyKey = trim((string) $request->post('idempotency_key', ''));
|
||||||
|
if ($withdrawCoin === '' || $receiveAccount === '' || $receiveType === '' || $idempotencyKey === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
$user = $this->auth->getUser();
|
||||||
|
if (bccomp((string) $user->coin, $withdrawCoin, 4) < 0) {
|
||||||
|
return $this->mobileError(2001, 'Insufficient balance');
|
||||||
|
}
|
||||||
|
|
||||||
|
$orderNo = 'WD' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
|
||||||
|
$feeCoin = bcmul($withdrawCoin, '0.005', 4);
|
||||||
|
$actualArrivalCoin = bcsub($withdrawCoin, $feeCoin, 4);
|
||||||
|
WithdrawOrder::create([
|
||||||
|
'order_no' => $orderNo,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'apply_amount' => $withdrawCoin,
|
||||||
|
'fee_amount' => $feeCoin,
|
||||||
|
'actual_amount' => $actualArrivalCoin,
|
||||||
|
'fiat_currency' => '',
|
||||||
|
'need_audit' => 1,
|
||||||
|
'audit_status' => 0,
|
||||||
|
'reject_reason' => '',
|
||||||
|
'create_time' => time(),
|
||||||
|
'update_time' => time(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'order_no' => $orderNo,
|
||||||
|
'status' => 'pending_review',
|
||||||
|
'fee_coin' => $feeCoin,
|
||||||
|
'actual_arrival_coin' => $actualArrivalCoin,
|
||||||
|
'risk_review_required' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withdrawDetail(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$orderNo = trim((string) $request->get('order_no', ''));
|
||||||
|
if ($orderNo === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
$order = WithdrawOrder::where('order_no', $orderNo)->where('user_id', $this->auth->id)->find();
|
||||||
|
if (!$order) {
|
||||||
|
return $this->mobileError(2003, 'Order does not exist');
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'order_no' => $order->order_no,
|
||||||
|
'status' => $this->mapWithdrawStatus($order->audit_status),
|
||||||
|
'withdraw_coin' => $order->apply_amount,
|
||||||
|
'fee_coin' => $order->fee_amount,
|
||||||
|
'reject_reason' => $order->reject_reason === '' ? null : $order->reject_reason,
|
||||||
|
'create_time' => $order->create_time,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mapDepositStatus($status): string
|
||||||
|
{
|
||||||
|
if ($this->intValue($status) === 1) {
|
||||||
|
return 'paid';
|
||||||
|
}
|
||||||
|
if ($this->intValue($status) === 2 || $this->intValue($status) === 3) {
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
return 'pending';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mapWithdrawStatus($auditStatus): string
|
||||||
|
{
|
||||||
|
if ($this->intValue($auditStatus) === 1) {
|
||||||
|
return 'approved';
|
||||||
|
}
|
||||||
|
if ($this->intValue($auditStatus) === 2) {
|
||||||
|
return 'rejected';
|
||||||
|
}
|
||||||
|
return 'pending_review';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function intValue($value): int
|
||||||
|
{
|
||||||
|
$result = filter_var($value, FILTER_VALIDATE_INT);
|
||||||
|
if ($result === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
320
app/api/controller/Game.php
Normal file
320
app/api/controller/Game.php
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\library\game\ZiHuaDictionary;
|
||||||
|
use app\common\model\BetOrder;
|
||||||
|
use app\common\model\GameConfig;
|
||||||
|
use app\common\model\GameRecord;
|
||||||
|
use app\common\model\UserWalletRecord;
|
||||||
|
use support\think\Db;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use support\Response;
|
||||||
|
|
||||||
|
class Game extends MobileBase
|
||||||
|
{
|
||||||
|
protected array $noNeedLogin = ['dictionaryList', 'periodHistory'];
|
||||||
|
|
||||||
|
public function lobbyInit(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$period = GameRecord::order('id', 'desc')->find();
|
||||||
|
$now = time();
|
||||||
|
$startAt = $period ? $this->intValue($period->period_start_at) : $now;
|
||||||
|
$lockAt = $startAt + 20;
|
||||||
|
$openAt = $startAt + 22;
|
||||||
|
$countdown = $period ? max(0, ($startAt + 30) - $now) : 0;
|
||||||
|
|
||||||
|
$dictionaryConfig = GameConfig::where('config_key', ZiHuaDictionary::CONFIG_KEY)->find();
|
||||||
|
$dictionaryItems = ZiHuaDictionary::parseFromConfigValue($dictionaryConfig?->config_value ?? null);
|
||||||
|
$items = [];
|
||||||
|
foreach ($dictionaryItems as $row) {
|
||||||
|
$items[] = [
|
||||||
|
'number' => $row['no'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'category' => $row['category'],
|
||||||
|
'icon' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->auth->getUser();
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'server_time' => $now,
|
||||||
|
'period' => [
|
||||||
|
'period_no' => $period->period_no ?? '',
|
||||||
|
'status' => $this->mapPeriodStatus($period->status ?? null),
|
||||||
|
'countdown' => $countdown,
|
||||||
|
'lock_at' => $lockAt,
|
||||||
|
'open_at' => $openAt,
|
||||||
|
],
|
||||||
|
'bet_config' => [
|
||||||
|
'max_select_count' => $this->intValue($this->getConfigValue('max_select_count', '5')),
|
||||||
|
'chips' => ['1.0000', '5.0000', '10.0000', '25.0000', '50.0000', '100.0000'],
|
||||||
|
'single_number_max_bet' => $this->getConfigValue('single_number_max_bet', '500.0000'),
|
||||||
|
],
|
||||||
|
'dictionary' => $items,
|
||||||
|
'user_snapshot' => [
|
||||||
|
'coin' => $user->coin,
|
||||||
|
'current_streak' => $user->current_streak ?? 0,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dictionaryList(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$dictionaryConfig = GameConfig::where('config_key', ZiHuaDictionary::CONFIG_KEY)->find();
|
||||||
|
$dictionaryItems = ZiHuaDictionary::parseFromConfigValue($dictionaryConfig?->config_value ?? null);
|
||||||
|
$items = [];
|
||||||
|
foreach ($dictionaryItems as $row) {
|
||||||
|
$items[] = [
|
||||||
|
'number' => $row['no'],
|
||||||
|
'name' => $row['name'],
|
||||||
|
'category' => $row['category'],
|
||||||
|
'icon' => '',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'version' => (string) ($dictionaryConfig->update_time ?? '1'),
|
||||||
|
'items' => $items,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function periodHistory(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$limit = $this->intValue($request->get('limit', 30));
|
||||||
|
if ($limit < 1) {
|
||||||
|
$limit = 30;
|
||||||
|
}
|
||||||
|
$list = GameRecord::whereNotNull('result_number')->order('id', 'desc')->limit($limit)->select();
|
||||||
|
$rows = [];
|
||||||
|
foreach ($list as $item) {
|
||||||
|
$rows[] = [
|
||||||
|
'period_no' => $item->period_no,
|
||||||
|
'result_number' => $item->result_number,
|
||||||
|
'open_time' => $item->update_time,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess(['list' => $rows]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function periodCurrent(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$period = GameRecord::order('id', 'desc')->find();
|
||||||
|
if (!$period) {
|
||||||
|
return $this->mobileError(2002, 'Game period does not exist');
|
||||||
|
}
|
||||||
|
$now = time();
|
||||||
|
$startAt = $this->intValue($period->period_start_at);
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'period_id' => $period->id,
|
||||||
|
'period_no' => $period->period_no,
|
||||||
|
'status' => $this->mapPeriodStatus($period->status),
|
||||||
|
'countdown' => max(0, ($startAt + 30) - $now),
|
||||||
|
'bet_close_in' => max(0, ($startAt + 20) - $now),
|
||||||
|
'result_number' => $period->result_number,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function betPlace(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$periodNo = trim((string) $request->post('period_no', ''));
|
||||||
|
$numbers = $request->post('numbers', []);
|
||||||
|
$betAmount = (string) $request->post('bet_amount', '');
|
||||||
|
$idempotencyKey = trim((string) $request->post('idempotency_key', ''));
|
||||||
|
if ($periodNo === '' || !is_array($numbers) || $betAmount === '' || $idempotencyKey === '') {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
if (count($numbers) < 1) {
|
||||||
|
return $this->mobileError(1003, 'Invalid parameter value');
|
||||||
|
}
|
||||||
|
|
||||||
|
$period = GameRecord::where('period_no', $periodNo)->find();
|
||||||
|
if (!$period) {
|
||||||
|
return $this->mobileError(2002, 'Game period does not exist');
|
||||||
|
}
|
||||||
|
if ($this->intValue($period->status) !== 0) {
|
||||||
|
return $this->mobileError(3002, 'Betting is closed');
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->auth->getUser();
|
||||||
|
$pickCount = count($numbers);
|
||||||
|
$totalAmount = bcmul($betAmount, (string) $pickCount, 4);
|
||||||
|
if (bccomp((string) $user->coin, $totalAmount, 4) < 0) {
|
||||||
|
return $this->mobileError(2001, 'Insufficient balance');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = BetOrder::where('idempotency_key', $idempotencyKey)->find();
|
||||||
|
if ($exists) {
|
||||||
|
return $this->mobileError(3003, 'Duplicate request');
|
||||||
|
}
|
||||||
|
|
||||||
|
Db::startTrans();
|
||||||
|
try {
|
||||||
|
$before = (string) $user->coin;
|
||||||
|
$after = bcsub($before, $totalAmount, 4);
|
||||||
|
UserWalletRecord::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'channel_id' => $user->channel_id,
|
||||||
|
'biz_type' => 'bet',
|
||||||
|
'direction' => 2,
|
||||||
|
'amount' => $totalAmount,
|
||||||
|
'balance_before' => $before,
|
||||||
|
'balance_after' => $after,
|
||||||
|
'ref_type' => 'bet_order',
|
||||||
|
'remark' => '移动端下注',
|
||||||
|
'create_time' => time(),
|
||||||
|
]);
|
||||||
|
Db::name('user')->where('id', $user->id)->update(['coin' => $after, 'update_time' => time()]);
|
||||||
|
$orderNo = 'BO' . date('YmdHis') . substr(str_replace('.', '', uniqid('', true)), -6);
|
||||||
|
BetOrder::create([
|
||||||
|
'period_id' => $period->id,
|
||||||
|
'period_no' => $period->period_no,
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'channel_id' => $user->channel_id,
|
||||||
|
'pick_numbers' => $numbers,
|
||||||
|
'unit_amount' => $betAmount,
|
||||||
|
'pick_count' => $pickCount,
|
||||||
|
'total_amount' => $totalAmount,
|
||||||
|
'streak_at_bet' => $user->current_streak ?? 0,
|
||||||
|
'is_auto' => 0,
|
||||||
|
'status' => 1,
|
||||||
|
'idempotency_key' => $idempotencyKey,
|
||||||
|
'create_time' => time(),
|
||||||
|
'update_time' => time(),
|
||||||
|
]);
|
||||||
|
Db::commit();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Db::rollback();
|
||||||
|
return $this->mobileError(5000, 'System is busy, please try again later', ['detail' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'order_no' => $orderNo,
|
||||||
|
'period_no' => $period->period_no,
|
||||||
|
'status' => 'accepted',
|
||||||
|
'locked_balance' => '0.0000',
|
||||||
|
'balance_after' => $after,
|
||||||
|
'current_streak' => $user->current_streak ?? 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function betRebet(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
return $this->mobileError(3001, 'Current process does not allow this operation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autoBetCreate(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
return $this->mobileError(3001, 'Current process does not allow this operation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function autoBetStop(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
return $this->mobileError(3001, 'Current process does not allow this operation');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function betMyOrders(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$page = $this->intValue($request->get('page', 1));
|
||||||
|
$pageSize = $this->intValue($request->get('page_size', 20));
|
||||||
|
$paginate = BetOrder::where('user_id', $this->auth->id)->order('id', 'desc')->paginate([
|
||||||
|
'page' => $page,
|
||||||
|
'list_rows' => $pageSize,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
foreach ($paginate->items() as $item) {
|
||||||
|
$rows[] = [
|
||||||
|
'order_no' => (string) $item->id,
|
||||||
|
'period_no' => $item->period_no,
|
||||||
|
'numbers' => $item->pick_numbers ?? [],
|
||||||
|
'bet_amount' => $item->unit_amount,
|
||||||
|
'total_amount' => $item->total_amount,
|
||||||
|
'result_number' => null,
|
||||||
|
'win_amount' => $item->win_amount,
|
||||||
|
'status' => (string) $item->status,
|
||||||
|
'create_time' => $item->create_time,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'list' => $rows,
|
||||||
|
'pagination' => [
|
||||||
|
'page' => $paginate->currentPage(),
|
||||||
|
'page_size' => $paginate->listRows(),
|
||||||
|
'total' => $paginate->total(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mapPeriodStatus($status): string
|
||||||
|
{
|
||||||
|
if ($this->intValue($status) === 0) {
|
||||||
|
return 'betting';
|
||||||
|
}
|
||||||
|
if ($this->intValue($status) === 1) {
|
||||||
|
return 'locked';
|
||||||
|
}
|
||||||
|
if ($this->intValue($status) === 2 || $this->intValue($status) === 3) {
|
||||||
|
return 'settling';
|
||||||
|
}
|
||||||
|
return 'finished';
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getConfigValue(string $key, string $default): string
|
||||||
|
{
|
||||||
|
$value = GameConfig::where('config_key', $key)->value('config_value');
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
return (string) $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function intValue($value): int
|
||||||
|
{
|
||||||
|
$result = filter_var($value, FILTER_VALIDATE_INT);
|
||||||
|
if ($result === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
72
app/api/controller/MobileBase.php
Normal file
72
app/api/controller/MobileBase.php
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\controller\Frontend;
|
||||||
|
use app\common\facade\Token;
|
||||||
|
use support\Response;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use function response;
|
||||||
|
|
||||||
|
abstract class MobileBase extends Frontend
|
||||||
|
{
|
||||||
|
protected array $noNeedPermission = ['*'];
|
||||||
|
protected array $noNeedAuthToken = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移动端统一初始化:
|
||||||
|
* - 校验请求头 auth-token
|
||||||
|
* - 再走会员中心 Frontend 初始化(登录态/权限等)
|
||||||
|
*/
|
||||||
|
protected function initializeMobile(Request $request): ?Response
|
||||||
|
{
|
||||||
|
$this->setRequest($request);
|
||||||
|
|
||||||
|
$path = trim($request->path(), '/');
|
||||||
|
$parts = explode('/', $path);
|
||||||
|
$action = $parts[array_key_last($parts)] ?? '';
|
||||||
|
$needAuthToken = !action_in_arr($this->noNeedAuthToken, $action);
|
||||||
|
if ($needAuthToken) {
|
||||||
|
$authToken = trim((string) $request->header('auth-token', ''));
|
||||||
|
if ($authToken === '') {
|
||||||
|
return $this->mobileError(1101, 'Missing auth-token');
|
||||||
|
}
|
||||||
|
$tokenData = Token::get($authToken);
|
||||||
|
$type = $tokenData['type'] ?? '';
|
||||||
|
$expireTime = $tokenData['expire_time'] ?? 0;
|
||||||
|
if ($type !== 'auth-token' || !is_numeric($expireTime) || $expireTime < time()) {
|
||||||
|
return $this->mobileError(1101, 'auth-token is invalid or expired');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->initializeFrontend($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function mobileSuccess(array $data = [], string $message = 'ok'): Response
|
||||||
|
{
|
||||||
|
if ($message === '') {
|
||||||
|
$message = __('ok');
|
||||||
|
} else {
|
||||||
|
$message = __($message);
|
||||||
|
}
|
||||||
|
$payload = [
|
||||||
|
'code' => 1,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data,
|
||||||
|
];
|
||||||
|
return response(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 200, ['Content-Type' => 'application/json']);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function mobileError(int $code, string $message, array $data = []): Response
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'code' => $code,
|
||||||
|
'message' => __($message),
|
||||||
|
'data' => $data,
|
||||||
|
];
|
||||||
|
return response(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 200, ['Content-Type' => 'application/json']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
123
app/api/controller/Notice.php
Normal file
123
app/api/controller/Notice.php
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\model\OperationNotice;
|
||||||
|
use app\common\model\UserNoticeRead;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use support\Response;
|
||||||
|
|
||||||
|
class Notice extends MobileBase
|
||||||
|
{
|
||||||
|
public function noticeList(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$page = $this->intValue($request->get('page', 1), 1);
|
||||||
|
$pageSize = $this->intValue($request->get('page_size', 20), 20);
|
||||||
|
|
||||||
|
$paginate = OperationNotice::where('status', 1)->order('id', 'desc')->paginate([
|
||||||
|
'page' => $page,
|
||||||
|
'list_rows' => $pageSize,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$noticeIds = [];
|
||||||
|
foreach ($paginate->items() as $row) {
|
||||||
|
$noticeIds[] = $row->id;
|
||||||
|
}
|
||||||
|
$readRows = [];
|
||||||
|
if ($noticeIds !== []) {
|
||||||
|
$readRows = UserNoticeRead::where('user_id', $this->auth->id)->whereIn('notice_id', $noticeIds)->column('notice_id');
|
||||||
|
}
|
||||||
|
$readMap = array_flip($readRows);
|
||||||
|
|
||||||
|
$list = [];
|
||||||
|
foreach ($paginate->items() as $row) {
|
||||||
|
$list[] = [
|
||||||
|
'notice_id' => $row->id,
|
||||||
|
'title' => $row->title,
|
||||||
|
'notice_type' => $this->intValue($row->notice_type, 0) === 1 ? 'popout' : 'silent',
|
||||||
|
'is_read' => isset($readMap[$row->id]),
|
||||||
|
'publish_time' => $row->publish_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->mobileSuccess(['list' => $list]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function noticeDetail(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$id = $this->intValue($request->get('id', 0), 0);
|
||||||
|
if ($id < 1) {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
$notice = OperationNotice::where('id', $id)->where('status', 1)->find();
|
||||||
|
if (!$notice) {
|
||||||
|
return $this->mobileError(2004, 'Notice does not exist');
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'notice_id' => $notice->id,
|
||||||
|
'title' => $notice->title,
|
||||||
|
'content' => $notice->content,
|
||||||
|
'notice_type' => $this->intValue($notice->notice_type, 0) === 1 ? 'popout' : 'silent',
|
||||||
|
'must_confirm' => $this->intValue($notice->notice_type, 0) === 1,
|
||||||
|
'publish_time' => $notice->publish_at,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function noticeConfirm(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$noticeId = $this->intValue($request->post('notice_id', 0), 0);
|
||||||
|
if ($noticeId < 1) {
|
||||||
|
return $this->mobileError(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
$notice = OperationNotice::where('id', $noticeId)->where('status', 1)->find();
|
||||||
|
if (!$notice) {
|
||||||
|
return $this->mobileError(2004, 'Notice does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$exists = UserNoticeRead::where('user_id', $this->auth->id)->where('notice_id', $noticeId)->find();
|
||||||
|
$now = time();
|
||||||
|
if ($exists) {
|
||||||
|
$exists->save([
|
||||||
|
'confirmed' => 1,
|
||||||
|
'read_at' => $now,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
UserNoticeRead::create([
|
||||||
|
'user_id' => $this->auth->id,
|
||||||
|
'notice_id' => $noticeId,
|
||||||
|
'confirmed' => 1,
|
||||||
|
'read_at' => $now,
|
||||||
|
'create_time' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'notice_id' => $noticeId,
|
||||||
|
'confirmed' => true,
|
||||||
|
'confirm_time' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function intValue($value, int $default): int
|
||||||
|
{
|
||||||
|
$result = filter_var($value, FILTER_VALIDATE_INT);
|
||||||
|
if ($result === false) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
85
app/api/controller/V1.php
Normal file
85
app/api/controller/V1.php
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\controller\Api;
|
||||||
|
use app\common\facade\Token;
|
||||||
|
use ba\Random;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use support\Response;
|
||||||
|
|
||||||
|
use function response;
|
||||||
|
|
||||||
|
class V1 extends Api
|
||||||
|
{
|
||||||
|
public function authToken(Request $request): Response
|
||||||
|
{
|
||||||
|
$responseInit = $this->initializeApi($request);
|
||||||
|
if ($responseInit !== null) {
|
||||||
|
return $responseInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$secret = trim((string) $request->get('secret', ''));
|
||||||
|
$timestampRaw = $request->get('timestamp', '');
|
||||||
|
$deviceId = trim((string) $request->get('device_id', ''));
|
||||||
|
$signature = trim((string) $request->get('signature', ''));
|
||||||
|
|
||||||
|
if ($secret === '' || $timestampRaw === '' || $deviceId === '' || $signature === '') {
|
||||||
|
return $this->mobileResult(1001, 'Missing parameters');
|
||||||
|
}
|
||||||
|
|
||||||
|
$serverSecret = (string) env('AUTH_TOKEN_SECRET', '');
|
||||||
|
if ($serverSecret === '' || !hash_equals($serverSecret, $secret)) {
|
||||||
|
return $this->mobileResult(1103, 'Invalid secret');
|
||||||
|
}
|
||||||
|
|
||||||
|
$timestamp = filter_var($timestampRaw, FILTER_VALIDATE_INT);
|
||||||
|
if ($timestamp === false) {
|
||||||
|
return $this->mobileResult(1002, 'Invalid parameter format');
|
||||||
|
}
|
||||||
|
$now = time();
|
||||||
|
$skew = abs($now - $timestamp);
|
||||||
|
if ($skew > 300) {
|
||||||
|
return $this->mobileResult(3001, 'Invalid timestamp');
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'device_id' => $deviceId,
|
||||||
|
'secret' => $secret,
|
||||||
|
'timestamp' => (string) $timestamp,
|
||||||
|
];
|
||||||
|
ksort($params);
|
||||||
|
$pairs = [];
|
||||||
|
foreach ($params as $k => $v) {
|
||||||
|
$pairs[] = $k . '=' . $v;
|
||||||
|
}
|
||||||
|
$plain = implode('&', $pairs);
|
||||||
|
$expected = strtoupper(md5($plain));
|
||||||
|
if (!hash_equals($expected, $signature)) {
|
||||||
|
return $this->mobileResult(1103, 'Invalid signature');
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = Random::uuid();
|
||||||
|
$expire = 60 * 60 * 24;
|
||||||
|
Token::set($token, 'auth-token', 0, $expire);
|
||||||
|
|
||||||
|
return $this->mobileResult(1, 'ok', [
|
||||||
|
'auth_token' => $token,
|
||||||
|
'expires_in' => $expire,
|
||||||
|
'server_time' => $now,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mobileResult(int $code, string $message, array $data = []): Response
|
||||||
|
{
|
||||||
|
$payload = [
|
||||||
|
'code' => $code,
|
||||||
|
'message' => __($message),
|
||||||
|
'data' => $data,
|
||||||
|
];
|
||||||
|
return response(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), 200, ['Content-Type' => 'application/json']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
80
app/api/controller/Wallet.php
Normal file
80
app/api/controller/Wallet.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace app\api\controller;
|
||||||
|
|
||||||
|
use app\common\model\UserWalletRecord;
|
||||||
|
use Webman\Http\Request;
|
||||||
|
use support\Response;
|
||||||
|
|
||||||
|
class Wallet extends MobileBase
|
||||||
|
{
|
||||||
|
public function balanceSummary(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$user = $this->auth->getUser();
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'coin_balance' => $user->coin,
|
||||||
|
'frozen_balance' => '0.0000',
|
||||||
|
'total_deposit_coin' => $user->total_deposit_coin ?? '0.0000',
|
||||||
|
'total_valid_bet_coin' => $user->total_valid_bet_coin ?? '0.0000',
|
||||||
|
'withdrawable_balance' => $user->coin,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordList(Request $request): Response
|
||||||
|
{
|
||||||
|
$response = $this->initializeMobile($request);
|
||||||
|
if ($response !== null) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
$type = trim((string) $request->get('type', 'all'));
|
||||||
|
$page = $this->intValue($request->get('page', 1), 1);
|
||||||
|
$pageSize = $this->intValue($request->get('page_size', 20), 20);
|
||||||
|
|
||||||
|
$query = UserWalletRecord::where('user_id', $this->auth->id)->order('id', 'desc');
|
||||||
|
if ($type !== '' && $type !== 'all') {
|
||||||
|
$query->where('biz_type', $type);
|
||||||
|
}
|
||||||
|
$paginate = $query->paginate([
|
||||||
|
'page' => $page,
|
||||||
|
'list_rows' => $pageSize,
|
||||||
|
]);
|
||||||
|
$list = [];
|
||||||
|
foreach ($paginate->items() as $row) {
|
||||||
|
$list[] = [
|
||||||
|
'record_id' => $row->id,
|
||||||
|
'biz_type' => $row->biz_type,
|
||||||
|
'direction' => $row->direction,
|
||||||
|
'amount' => $row->amount,
|
||||||
|
'balance_before' => $row->balance_before,
|
||||||
|
'balance_after' => $row->balance_after,
|
||||||
|
'ref_type' => $row->ref_type,
|
||||||
|
'ref_id' => $row->ref_id,
|
||||||
|
'create_time' => $row->create_time,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $this->mobileSuccess([
|
||||||
|
'list' => $list,
|
||||||
|
'pagination' => [
|
||||||
|
'page' => $paginate->currentPage(),
|
||||||
|
'page_size' => $paginate->listRows(),
|
||||||
|
'total' => $paginate->total(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function intValue($value, int $default): int
|
||||||
|
{
|
||||||
|
$result = filter_var($value, FILTER_VALIDATE_INT);
|
||||||
|
if ($result === false) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -12,6 +12,27 @@ return [
|
|||||||
'Please login first' => 'Please login first!',
|
'Please login first' => 'Please login first!',
|
||||||
'You have no permission' => 'No permission to operate!',
|
'You have no permission' => 'No permission to operate!',
|
||||||
'Captcha error' => 'Captcha error!',
|
'Captcha error' => 'Captcha error!',
|
||||||
|
'ok' => 'ok',
|
||||||
|
'Missing parameters' => 'Missing parameters',
|
||||||
|
'Invalid parameter format' => 'Invalid parameter format',
|
||||||
|
'Invalid parameter value' => 'Invalid parameter value',
|
||||||
|
'Missing auth-token' => 'Missing auth-token',
|
||||||
|
'auth-token is invalid or expired' => 'auth-token is invalid or expired',
|
||||||
|
'Invalid secret' => 'Invalid secret',
|
||||||
|
'Invalid signature' => 'Invalid signature',
|
||||||
|
'Invalid timestamp' => 'Invalid timestamp',
|
||||||
|
'Invite code does not exist' => 'Invite code does not exist',
|
||||||
|
'Registered successfully but login failed' => 'Registered successfully but login failed',
|
||||||
|
'Incorrect account or password' => 'Incorrect account or password',
|
||||||
|
'Login status has expired' => 'Login status has expired',
|
||||||
|
'Game period does not exist' => 'Game period does not exist',
|
||||||
|
'Betting is closed' => 'Betting is closed',
|
||||||
|
'Insufficient balance' => 'Insufficient balance',
|
||||||
|
'Duplicate request' => 'Duplicate request',
|
||||||
|
'System is busy, please try again later' => 'System is busy, please try again later',
|
||||||
|
'Current process does not allow this operation' => 'Current process does not allow this operation',
|
||||||
|
'Order does not exist' => 'Order does not exist',
|
||||||
|
'Notice does not exist' => 'Notice does not exist',
|
||||||
// Member center account
|
// Member center account
|
||||||
'Data updated successfully~' => 'Data updated successfully~',
|
'Data updated successfully~' => 'Data updated successfully~',
|
||||||
'Password has been changed~' => 'Password has been changed~',
|
'Password has been changed~' => 'Password has been changed~',
|
||||||
|
|||||||
@@ -44,6 +44,27 @@ return [
|
|||||||
'Parameter error' => '参数错误!',
|
'Parameter error' => '参数错误!',
|
||||||
'Token expiration' => '登录态过期,请重新登录!',
|
'Token expiration' => '登录态过期,请重新登录!',
|
||||||
'Captcha error' => '验证码错误!',
|
'Captcha error' => '验证码错误!',
|
||||||
|
'ok' => '成功',
|
||||||
|
'Missing parameters' => '参数缺失',
|
||||||
|
'Invalid parameter format' => '参数格式错误',
|
||||||
|
'Invalid parameter value' => '参数取值非法',
|
||||||
|
'Missing auth-token' => '缺少 auth-token',
|
||||||
|
'auth-token is invalid or expired' => 'auth-token 无效或已过期',
|
||||||
|
'Invalid secret' => '密钥无效',
|
||||||
|
'Invalid signature' => '签名错误',
|
||||||
|
'Invalid timestamp' => '时间戳无效',
|
||||||
|
'Invite code does not exist' => '邀请码不存在',
|
||||||
|
'Registered successfully but login failed' => '注册成功但登录失败',
|
||||||
|
'Incorrect account or password' => '账号或密码错误',
|
||||||
|
'Login status has expired' => '登录状态已过期',
|
||||||
|
'Game period does not exist' => '对局不存在',
|
||||||
|
'Betting is closed' => '已封盘,禁止下注',
|
||||||
|
'Insufficient balance' => '余额不足',
|
||||||
|
'Duplicate request' => '重复请求(幂等冲突)',
|
||||||
|
'System is busy, please try again later' => '系统繁忙,请稍后重试',
|
||||||
|
'Current process does not allow this operation' => '当前流程不允许该操作',
|
||||||
|
'Order does not exist' => '订单不存在',
|
||||||
|
'Notice does not exist' => '公告不存在',
|
||||||
// 会员中心 account
|
// 会员中心 account
|
||||||
'Data updated successfully~' => '资料更新成功~',
|
'Data updated successfully~' => '资料更新成功~',
|
||||||
'Password has been changed~' => '密码已修改~',
|
'Password has been changed~' => '密码已修改~',
|
||||||
|
|||||||
@@ -25,12 +25,20 @@ class LoadLangPack implements MiddlewareInterface
|
|||||||
|
|
||||||
protected function loadLang(Request $request): void
|
protected function loadLang(Request $request): void
|
||||||
{
|
{
|
||||||
// 优先从请求头 think-lang 获取前端选择的语言(与前端 axios 发送的 header 对应)
|
// 优先从请求头 lang / think-lang 获取前端选择的语言
|
||||||
// 安装页等未发送 think-lang 时,回退到 Accept-Language 或配置默认值
|
// 支持:lang=en / lang=zh / think-lang=en / think-lang=zh-cn
|
||||||
$headerLang = $request->header('think-lang');
|
// 未发送时回退到 Accept-Language 或配置默认值
|
||||||
|
$headerLang = $request->header('lang', '');
|
||||||
|
if ($headerLang === '') {
|
||||||
|
$headerLang = $request->header('think-lang', '');
|
||||||
|
}
|
||||||
$allowLangList = config('lang.allow_lang_list', ['zh-cn', 'en']);
|
$allowLangList = config('lang.allow_lang_list', ['zh-cn', 'en']);
|
||||||
if ($headerLang && in_array(str_replace('_', '-', strtolower($headerLang)), $allowLangList)) {
|
$normalizedHeaderLang = str_replace('_', '-', strtolower($headerLang));
|
||||||
$langSet = str_replace('_', '-', strtolower($headerLang));
|
if ($normalizedHeaderLang === 'zh') {
|
||||||
|
$normalizedHeaderLang = 'zh-cn';
|
||||||
|
}
|
||||||
|
if ($headerLang && in_array($normalizedHeaderLang, $allowLangList)) {
|
||||||
|
$langSet = $normalizedHeaderLang;
|
||||||
} else {
|
} else {
|
||||||
$acceptLang = $request->header('accept-language', '');
|
$acceptLang = $request->header('accept-language', '');
|
||||||
if (preg_match('/^zh[-_]?cn|^zh/i', $acceptLang)) {
|
if (preg_match('/^zh[-_]?cn|^zh/i', $acceptLang)) {
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ Route::get('/install/index', function () use ($installLockFileForInstall, $insta
|
|||||||
// api/index
|
// api/index
|
||||||
Route::get('/api/index/index', [\app\api\controller\Index::class, 'index']);
|
Route::get('/api/index/index', [\app\api\controller\Index::class, 'index']);
|
||||||
|
|
||||||
|
// api/v1
|
||||||
|
Route::get('/api/v1/authToken', [\app\api\controller\V1::class, 'authToken']);
|
||||||
|
|
||||||
// api/user(GET 获取配置,POST 登录/注册)
|
// api/user(GET 获取配置,POST 登录/注册)
|
||||||
Route::add(['GET', 'POST'], '/api/user/checkIn', [\app\api\controller\User::class, 'checkIn']);
|
Route::add(['GET', 'POST'], '/api/user/checkIn', [\app\api\controller\User::class, 'checkIn']);
|
||||||
Route::post('/api/user/logout', [\app\api\controller\User::class, 'logout']);
|
Route::post('/api/user/logout', [\app\api\controller\User::class, 'logout']);
|
||||||
@@ -108,6 +111,35 @@ Route::post('/api/account/retrievePassword', [\app\api\controller\Account::class
|
|||||||
// api/ems
|
// api/ems
|
||||||
Route::post('/api/ems/send', [\app\api\controller\Ems::class, 'send']);
|
Route::post('/api/ems/send', [\app\api\controller\Ems::class, 'send']);
|
||||||
|
|
||||||
|
// ==================== 移动端游戏接口(36字花) ====================
|
||||||
|
Route::post('/api/auth/userRegister', [\app\api\controller\Auth::class, 'userRegister']);
|
||||||
|
Route::post('/api/auth/userLogin', [\app\api\controller\Auth::class, 'userLogin']);
|
||||||
|
Route::post('/api/auth/tokenRefresh', [\app\api\controller\Auth::class, 'tokenRefresh']);
|
||||||
|
|
||||||
|
Route::get('/api/account/userProfile', [\app\api\controller\Account::class, 'userProfile']);
|
||||||
|
|
||||||
|
Route::get('/api/game/lobbyInit', [\app\api\controller\Game::class, 'lobbyInit']);
|
||||||
|
Route::get('/api/game/dictionaryList', [\app\api\controller\Game::class, 'dictionaryList']);
|
||||||
|
Route::get('/api/game/periodHistory', [\app\api\controller\Game::class, 'periodHistory']);
|
||||||
|
Route::get('/api/game/periodCurrent', [\app\api\controller\Game::class, 'periodCurrent']);
|
||||||
|
Route::post('/api/game/betPlace', [\app\api\controller\Game::class, 'betPlace']);
|
||||||
|
Route::post('/api/game/betRebet', [\app\api\controller\Game::class, 'betRebet']);
|
||||||
|
Route::post('/api/game/autoBetCreate', [\app\api\controller\Game::class, 'autoBetCreate']);
|
||||||
|
Route::post('/api/game/autoBetStop', [\app\api\controller\Game::class, 'autoBetStop']);
|
||||||
|
Route::get('/api/game/betMyOrders', [\app\api\controller\Game::class, 'betMyOrders']);
|
||||||
|
|
||||||
|
Route::get('/api/wallet/balanceSummary', [\app\api\controller\Wallet::class, 'balanceSummary']);
|
||||||
|
Route::get('/api/wallet/recordList', [\app\api\controller\Wallet::class, 'recordList']);
|
||||||
|
|
||||||
|
Route::post('/api/finance/depositCreate', [\app\api\controller\Finance::class, 'depositCreate']);
|
||||||
|
Route::get('/api/finance/depositDetail', [\app\api\controller\Finance::class, 'depositDetail']);
|
||||||
|
Route::post('/api/finance/withdrawCreate', [\app\api\controller\Finance::class, 'withdrawCreate']);
|
||||||
|
Route::get('/api/finance/withdrawDetail', [\app\api\controller\Finance::class, 'withdrawDetail']);
|
||||||
|
|
||||||
|
Route::get('/api/notice/noticeList', [\app\api\controller\Notice::class, 'noticeList']);
|
||||||
|
Route::get('/api/notice/noticeDetail', [\app\api\controller\Notice::class, 'noticeDetail']);
|
||||||
|
Route::post('/api/notice/noticeConfirm', [\app\api\controller\Notice::class, 'noticeConfirm']);
|
||||||
|
|
||||||
// ==================== Admin 路由 ====================
|
// ==================== Admin 路由 ====================
|
||||||
// Admin 多为 JSON API,前端可能用 GET 传参查列表、POST 提交表单,使用 any 确保兼容
|
// Admin 多为 JSON API,前端可能用 GET 传参查列表、POST 提交表单,使用 any 确保兼容
|
||||||
|
|
||||||
|
|||||||
36
scripts/generate_auth_signature.php
Normal file
36
scripts/generate_auth_signature.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行方法
|
||||||
|
* php scripts/generate_auth_signature.php
|
||||||
|
* php scripts/generate_auth_signature.php 设备码 密钥 时间戳
|
||||||
|
* php scripts/generate_auth_signature.php 1 564d14asdasd113e46542asd6das1a2a 1776331077
|
||||||
|
*/
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
$deviceId = $argv[1] ?? '1';
|
||||||
|
$secret = $argv[2] ?? ((string) getenv('AUTH_TOKEN_SECRET') ?: '564d14asdasd113e46542asd6das1a2a');
|
||||||
|
$timestamp = $argv[3] ?? (string) time();
|
||||||
|
|
||||||
|
$params = [
|
||||||
|
'device_id' => (string) $deviceId,
|
||||||
|
'secret' => (string) $secret,
|
||||||
|
'timestamp' => (string) $timestamp,
|
||||||
|
];
|
||||||
|
|
||||||
|
ksort($params);
|
||||||
|
|
||||||
|
$pairs = [];
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
$pairs[] = $key . '=' . $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$plain = implode('&', $pairs);
|
||||||
|
$signature = strtoupper(md5($plain));
|
||||||
|
|
||||||
|
echo 'device_id: ' . $params['device_id'] . PHP_EOL;
|
||||||
|
echo 'secret: ' . $params['secret'] . PHP_EOL;
|
||||||
|
echo 'timestamp: ' . $params['timestamp'] . PHP_EOL;
|
||||||
|
echo 'signature: ' . $signature . PHP_EOL;
|
||||||
|
echo 'url: /api/v1/authToken?secret=' . rawurlencode($params['secret']) . '×tamp=' . rawurlencode($params['timestamp']) . '&device_id=' . rawurlencode($params['device_id']) . '&signature=' . rawurlencode($signature) . PHP_EOL;
|
||||||
|
|
||||||
Reference in New Issue
Block a user