API接口-初版

This commit is contained in:
2026-04-16 17:38:21 +08:00
parent 7b39a2a505
commit 545a818094
14 changed files with 1163 additions and 5 deletions

320
app/api/controller/Game.php Normal file
View 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;
}
}