API接口-初版
This commit is contained in:
@@ -10,6 +10,7 @@ use app\common\facade\Token;
|
||||
use app\common\model\UserScoreLog;
|
||||
use app\common\model\UserMoneyLog;
|
||||
use app\common\controller\Frontend;
|
||||
use app\common\facade\Token as TokenFacade;
|
||||
use support\validation\Validator;
|
||||
use support\validation\ValidationException;
|
||||
use Webman\Http\Request;
|
||||
@@ -20,6 +21,51 @@ class Account extends Frontend
|
||||
protected array $noNeedLogin = ['retrievePassword'];
|
||||
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
|
||||
{
|
||||
$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!',
|
||||
'You have no permission' => 'No permission to operate!',
|
||||
'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
|
||||
'Data updated successfully~' => 'Data updated successfully~',
|
||||
'Password has been changed~' => 'Password has been changed~',
|
||||
|
||||
@@ -44,6 +44,27 @@ return [
|
||||
'Parameter error' => '参数错误!',
|
||||
'Token expiration' => '登录态过期,请重新登录!',
|
||||
'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
|
||||
'Data updated successfully~' => '资料更新成功~',
|
||||
'Password has been changed~' => '密码已修改~',
|
||||
|
||||
Reference in New Issue
Block a user