1.优化下注接口/api/game/betPlace
2.优化后台/admin/config/gameConfig中新增压注筹码配置
This commit is contained in:
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\library\game\BetChips;
|
||||
use app\common\library\game\ZiHuaDictionary;
|
||||
use app\common\model\BetOrder;
|
||||
use app\common\model\GameRecord;
|
||||
@@ -57,12 +58,14 @@ class Game extends MobileBase
|
||||
'lock_at' => $lockAt,
|
||||
'open_at' => $openAt,
|
||||
],
|
||||
'bet_config' => [
|
||||
'pick_max_number_count' => $this->getPickMaxNumberCount(),
|
||||
'chips' => ['1.00', '5.00', '10.00', '25.00', '50.00', '100.00'],
|
||||
'min_bet_per_number' => $this->getConfigValue('min_bet_per_number', '0.0100'),
|
||||
'max_bet_per_number' => $this->getConfigValue('max_bet_per_number', '10000.0000'),
|
||||
],
|
||||
'bet_config' => array_merge(
|
||||
[
|
||||
'pick_max_number_count' => $this->getPickMaxNumberCount(),
|
||||
'min_bet_per_number' => $this->getConfigValue('min_bet_per_number', '0.0100'),
|
||||
'max_bet_per_number' => $this->getConfigValue('max_bet_per_number', '10000.0000'),
|
||||
],
|
||||
BetChips::lobbyChipsPayload()
|
||||
),
|
||||
'dictionary' => $items,
|
||||
'user_snapshot' => [
|
||||
'coin' => $user->coin,
|
||||
@@ -119,7 +122,7 @@ class Game extends MobileBase
|
||||
|
||||
/**
|
||||
* 兼容旧路由:/api/game/betPlace
|
||||
* 新语义与 place_bet 一致:bet_amount 作为“单注金额”。
|
||||
* 与 placeBet 一致:须传筹码标识 bet_id(1–6),单注金额取自后台 game_config 的 bet_chips。
|
||||
*/
|
||||
public function betPlace(Request $request): Response
|
||||
{
|
||||
@@ -127,8 +130,7 @@ class Game extends MobileBase
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交下注:入参为 period_no + numbers + single_bet_amount + idempotency_key。
|
||||
* 兼容前端传参 bet_amount(作为 single_bet_amount 同义字段)。
|
||||
* 提交下注:入参为 period_no + numbers + bet_id(1–6,对应 lobbyInit.bet_config.chips 的键)+ idempotency_key。
|
||||
*/
|
||||
public function placeBet(Request $request): Response
|
||||
{
|
||||
@@ -138,9 +140,14 @@ class Game extends MobileBase
|
||||
}
|
||||
$periodNo = trim((string) $request->post('period_no', ''));
|
||||
$numbersRaw = $request->post('numbers', '');
|
||||
$singleBetAmount = trim((string) ($request->post('single_bet_amount', $request->post('bet_amount', ''))));
|
||||
$idempotencyKey = trim((string) $request->post('idempotency_key', ''));
|
||||
if ($periodNo === '' || $singleBetAmount === '' || $idempotencyKey === '') {
|
||||
$chipPick = $this->resolveBetChipFromRequest($request);
|
||||
if (isset($chipPick['error'])) {
|
||||
return $chipPick['error'];
|
||||
}
|
||||
$betChipId = $chipPick['bet_id'];
|
||||
$singleBetAmount = $chipPick['amount'];
|
||||
if ($periodNo === '' || $idempotencyKey === '') {
|
||||
return $this->mobileError(1001, 'Missing parameters');
|
||||
}
|
||||
if (!is_numeric($singleBetAmount) || bccomp($singleBetAmount, '0', 2) <= 0) {
|
||||
@@ -275,6 +282,7 @@ class Game extends MobileBase
|
||||
'user_id' => $userId,
|
||||
'period_no' => $period->period_no,
|
||||
'numbers' => $numbers,
|
||||
'bet_id' => $betChipId,
|
||||
'single_bet_amount' => $singleAmount,
|
||||
'numbers_count' => count($numbers),
|
||||
'total_amount' => $totalAmount,
|
||||
@@ -291,6 +299,7 @@ class Game extends MobileBase
|
||||
'order_no' => $orderNo,
|
||||
'period_no' => $period->period_no,
|
||||
'status' => 'accepted',
|
||||
'bet_id' => $betChipId,
|
||||
'single_bet_amount' => $singleAmount,
|
||||
'numbers_count' => count($numbers),
|
||||
'locked_balance' => '0.00',
|
||||
@@ -321,9 +330,13 @@ class Game extends MobileBase
|
||||
|
||||
$periodNo = trim((string) $request->post('period_no', ''));
|
||||
$numbersRaw = $request->post('numbers', '');
|
||||
$singleBetAmount = trim((string) ($request->post('single_bet_amount', $request->post('bet_amount', ''))));
|
||||
$rounds = $this->intValue($request->post('rounds', 1));
|
||||
if ($periodNo === '' || $singleBetAmount === '' || $rounds < 1) {
|
||||
$chipPick = $this->resolveBetChipFromRequest($request);
|
||||
if (isset($chipPick['error'])) {
|
||||
return $chipPick['error'];
|
||||
}
|
||||
$singleBetAmount = $chipPick['amount'];
|
||||
if ($periodNo === '' || $rounds < 1) {
|
||||
return $this->mobileError(1001, 'Missing parameters');
|
||||
}
|
||||
if (!is_numeric($singleBetAmount) || bccomp($singleBetAmount, '0', 2) <= 0) {
|
||||
@@ -340,6 +353,7 @@ class Game extends MobileBase
|
||||
GameWebSocketEventBus::publish('auto.spin.progress', [
|
||||
'user_id' => $userIdValue,
|
||||
'period_no' => $periodNo,
|
||||
'bet_id' => $chipPick['bet_id'],
|
||||
'rounds' => $rounds,
|
||||
'remaining_rounds' => $rounds,
|
||||
'completed_rounds' => 0,
|
||||
@@ -351,6 +365,7 @@ class Game extends MobileBase
|
||||
'auto_mode' => true,
|
||||
'period_no' => $periodNo,
|
||||
'numbers' => $numbers,
|
||||
'bet_id' => $chipPick['bet_id'],
|
||||
'single_bet_amount' => bcadd($singleBetAmount, '0', 2),
|
||||
'rounds' => $rounds,
|
||||
'remaining_rounds' => $rounds,
|
||||
@@ -376,7 +391,7 @@ class Game extends MobileBase
|
||||
'order_no' => (string) $item->id,
|
||||
'period_no' => $item->period_no,
|
||||
'numbers' => $item->pick_numbers ?? [],
|
||||
// 整笔压注金额(与请求 bet_amount 语义一致)
|
||||
// 整笔压注金额(本笔总扣款)
|
||||
'bet_amount' => $item->total_amount,
|
||||
'total_amount' => $item->total_amount,
|
||||
'result_number' => null,
|
||||
@@ -456,6 +471,28 @@ class Game extends MobileBase
|
||||
return 'finished';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{bet_id: int, amount: string}|array{error: Response}
|
||||
*/
|
||||
private function resolveBetChipFromRequest(Request $request): array
|
||||
{
|
||||
$betIdRaw = $request->post('bet_id', '');
|
||||
if ($betIdRaw === '' || $betIdRaw === null) {
|
||||
return ['error' => $this->mobileError(1001, 'Missing parameters')];
|
||||
}
|
||||
$betId = filter_var($betIdRaw, FILTER_VALIDATE_INT);
|
||||
if ($betId === false || $betId < 1 || $betId > 6) {
|
||||
return ['error' => $this->mobileError(1003, 'Invalid parameter value')];
|
||||
}
|
||||
$resolved = BetChips::resolveFromHotData();
|
||||
$amount = BetChips::amountForBetId($betId, $resolved['map']);
|
||||
if ($amount === null) {
|
||||
return ['error' => $this->mobileError(1003, 'Invalid parameter value')];
|
||||
}
|
||||
|
||||
return ['bet_id' => $betId, 'amount' => $amount];
|
||||
}
|
||||
|
||||
/**
|
||||
* 单注最多可选号码个数:`game_config.config_key = pick_max_number_count`
|
||||
*/
|
||||
|
||||
@@ -25,12 +25,15 @@ return [
|
||||
'Register only supports phone' => 'Register only supports phone',
|
||||
'Invite code required' => 'Invite code is required',
|
||||
'Invite code not bound to channel' => 'This invite code is not bound to a valid channel',
|
||||
'Channel disabled' => 'Channel is disabled',
|
||||
'Account already registered' => 'This phone number is already registered. Please sign in.',
|
||||
'Please enter the correct mobile number' => 'Please enter the correct mobile number',
|
||||
'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',
|
||||
'Game is paused' => 'Game is paused',
|
||||
'Bet amount out of range' => 'Bet amount out of range',
|
||||
'Betting is closed' => 'Betting is closed',
|
||||
'Insufficient balance' => 'Insufficient balance',
|
||||
'Duplicate request' => 'Duplicate request',
|
||||
|
||||
@@ -57,12 +57,15 @@ return [
|
||||
'Register only supports phone' => '注册仅支持手机号',
|
||||
'Invite code required' => '请填写邀请码',
|
||||
'Invite code not bound to channel' => '该邀请码未绑定有效渠道',
|
||||
'Channel disabled' => '渠道已关闭',
|
||||
'Account already registered' => '该手机号已注册,请直接登录',
|
||||
'Please enter the correct mobile number' => '请输入正确的手机号',
|
||||
'Registered successfully but login failed' => '注册成功但登录失败',
|
||||
'Incorrect account or password' => '账号或密码错误',
|
||||
'Login status has expired' => '登录状态已过期',
|
||||
'Game period does not exist' => '对局不存在',
|
||||
'Game is paused' => '游戏已暂停(维护或已关闭运行开关)',
|
||||
'Bet amount out of range' => '单注金额超出允许范围',
|
||||
'Betting is closed' => '已封盘,禁止下注',
|
||||
'Insufficient balance' => '余额不足',
|
||||
'Duplicate request' => '重复请求(幂等冲突)',
|
||||
|
||||
148
app/common/library/game/BetChips.php
Normal file
148
app/common/library/game/BetChips.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\library\game;
|
||||
|
||||
use app\common\service\GameHotDataRedis;
|
||||
|
||||
/**
|
||||
* 移动端快捷筹码(固定 1–6 档)及默认选中档位,对应 game_config:`bet_chips`(JSON)、`default_bet_chip_id`(int)。
|
||||
*/
|
||||
final class BetChips
|
||||
{
|
||||
public const CONFIG_KEY_CHIPS = 'bet_chips';
|
||||
|
||||
public const CONFIG_KEY_DEFAULT_ID = 'default_bet_chip_id';
|
||||
|
||||
/**
|
||||
* @return array<int, string> 键 1..6 => 两位小数字符串面额
|
||||
*/
|
||||
public static function defaultChipAmounts(): array
|
||||
{
|
||||
return [
|
||||
1 => '1.00',
|
||||
2 => '5.00',
|
||||
3 => '10.00',
|
||||
4 => '25.00',
|
||||
5 => '50.00',
|
||||
6 => '100.00',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{map: array<int, string>, default_id: int}
|
||||
*/
|
||||
public static function resolveFromHotData(): array
|
||||
{
|
||||
$rowChips = GameHotDataRedis::gameConfigRow(self::CONFIG_KEY_CHIPS);
|
||||
$rowDefault = GameHotDataRedis::gameConfigRow(self::CONFIG_KEY_DEFAULT_ID);
|
||||
$rawJson = $rowChips !== null ? ($rowChips['config_value'] ?? null) : null;
|
||||
$map = self::parseChipsJson($rawJson);
|
||||
|
||||
$defaultRaw = $rowDefault !== null ? ($rowDefault['config_value'] ?? null) : null;
|
||||
$defaultId = filter_var(trim('' . $defaultRaw), FILTER_VALIDATE_INT);
|
||||
if ($defaultId === false || $defaultId < 1 || $defaultId > 6) {
|
||||
$defaultId = 1;
|
||||
}
|
||||
$amt = $map[$defaultId] ?? null;
|
||||
if ($amt === null || !is_numeric($amt) || bccomp(bcadd($amt, '0', 2), '0', 2) <= 0) {
|
||||
$defaultId = self::firstPositiveBetId($map);
|
||||
}
|
||||
|
||||
return ['map' => $map, 'default_id' => $defaultId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $map
|
||||
*/
|
||||
public static function amountForBetId(int $betId, array $map): ?string
|
||||
{
|
||||
if ($betId < 1 || $betId > 6) {
|
||||
return null;
|
||||
}
|
||||
if (!isset($map[$betId])) {
|
||||
return null;
|
||||
}
|
||||
$amt = $map[$betId];
|
||||
if (!is_numeric($amt) || bccomp(bcadd($amt, '0', 2), '0', 2) <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return bcadd($amt, '0', 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* lobbyInit 用:筹码为字典,键为标识字符串 `"1"`…`"6"`,值为两位小数字符串面额。
|
||||
*
|
||||
* @return array{chips: array<string, string>, default_bet_chip_id: int}
|
||||
*/
|
||||
public static function lobbyChipsPayload(): array
|
||||
{
|
||||
$resolved = self::resolveFromHotData();
|
||||
$chips = [];
|
||||
foreach ($resolved['map'] as $id => $amount) {
|
||||
$chips['' . $id] = $amount;
|
||||
}
|
||||
|
||||
return [
|
||||
'chips' => $chips,
|
||||
'default_bet_chip_id' => $resolved['default_id'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $rawJson
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private static function parseChipsJson($rawJson): array
|
||||
{
|
||||
$defaults = self::defaultChipAmounts();
|
||||
$parsed = [];
|
||||
if (is_string($rawJson) && $rawJson !== '') {
|
||||
$decoded = json_decode($rawJson, true);
|
||||
if (is_array($decoded)) {
|
||||
foreach ($decoded as $k => $v) {
|
||||
$id = filter_var($k, FILTER_VALIDATE_INT);
|
||||
if ($id === false || $id < 1 || $id > 6) {
|
||||
continue;
|
||||
}
|
||||
$amtRaw = trim('' . $v);
|
||||
if (!is_numeric($amtRaw) || bccomp(bcadd($amtRaw, '0', 2), '0', 2) <= 0) {
|
||||
continue;
|
||||
}
|
||||
$parsed[$id] = bcadd($amtRaw, '0', 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
$out = [];
|
||||
foreach ($defaults as $id => $def) {
|
||||
if (isset($parsed[$id])) {
|
||||
$out[$id] = $parsed[$id];
|
||||
} else {
|
||||
$out[$id] = $def;
|
||||
}
|
||||
}
|
||||
ksort($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $map
|
||||
*/
|
||||
private static function firstPositiveBetId(array $map): int
|
||||
{
|
||||
for ($i = 1; $i <= 6; $i++) {
|
||||
if (!isset($map[$i])) {
|
||||
continue;
|
||||
}
|
||||
$a = $map[$i];
|
||||
if (is_numeric($a) && bccomp(bcadd($a, '0', 2), '0', 2) > 0) {
|
||||
return $i;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class LoadLangPack implements MiddlewareInterface
|
||||
/**
|
||||
* 解析当前请求语言。
|
||||
* - 后台 admin:优先请求头 think-lang(zh-cn / en),其次 lang 头,再次查询/表单参数 lang(支持 zh→zh-cn)。
|
||||
* - 对外 api:优先查询/表单参数 lang(zh / en),其次 lang 头,再次 think-lang;未显式指定时固定 zh-cn(不使用 Accept-Language)。
|
||||
* - 对外 api:优先查询/表单参数 lang(zh / zh-cn / en),其次 lang 头,再次 think-lang;仅当解析结果为允许列表中的 zh-cn 或 en 时生效,否则固定 zh-cn(不使用 Accept-Language)。仅 lang=en(规范化后)返回英文文案。
|
||||
*/
|
||||
protected function resolveLangSet(Request $request): string
|
||||
{
|
||||
@@ -38,13 +38,28 @@ class LoadLangPack implements MiddlewareInterface
|
||||
if ($queryRaw === null || $queryRaw === '') {
|
||||
$queryRaw = $request->post('lang');
|
||||
}
|
||||
$queryLang = is_string($queryRaw) ? $queryRaw : '';
|
||||
$queryLang = '';
|
||||
if (is_string($queryRaw)) {
|
||||
$queryLang = $queryRaw;
|
||||
} elseif (is_scalar($queryRaw)) {
|
||||
$queryLang = trim('' . $queryRaw);
|
||||
}
|
||||
|
||||
$thinkRaw = $request->header('think-lang');
|
||||
$thinkLang = is_string($thinkRaw) ? $thinkRaw : '';
|
||||
$thinkRaw = $request->header('think-lang', '');
|
||||
$thinkLang = '';
|
||||
if (is_string($thinkRaw)) {
|
||||
$thinkLang = $thinkRaw;
|
||||
} elseif (is_array($thinkRaw) && isset($thinkRaw[0]) && is_string($thinkRaw[0])) {
|
||||
$thinkLang = $thinkRaw[0];
|
||||
}
|
||||
|
||||
$headerLangRaw = $request->header('lang');
|
||||
$headerLang = is_string($headerLangRaw) ? $headerLangRaw : '';
|
||||
$headerLangRaw = $request->header('lang', '');
|
||||
$headerLang = '';
|
||||
if (is_string($headerLangRaw)) {
|
||||
$headerLang = $headerLangRaw;
|
||||
} elseif (is_array($headerLangRaw) && isset($headerLangRaw[0]) && is_string($headerLangRaw[0])) {
|
||||
$headerLang = $headerLangRaw[0];
|
||||
}
|
||||
|
||||
$normalize = static function (string $raw): string {
|
||||
$s = str_replace('_', '-', strtolower(trim($raw)));
|
||||
|
||||
Reference in New Issue
Block a user