修复聊天室字号偏好和游戏通知显示
This commit is contained in:
@@ -185,9 +185,10 @@ class DailySignInController extends Controller
|
||||
.',当前连续签到 '.$streakDays.' 天,获得 '.$rewardText.$identityText.'。';
|
||||
}
|
||||
|
||||
// 聊天消息内的快捷按钮使用相对字号,避免覆盖用户选择的消息字号。
|
||||
$quickButton = '<button type="button" onclick="window.quickDailySignIn && window.quickDailySignIn()" '
|
||||
.'style="display:inline-block;margin-left:6px;padding:1px 8px;border:none;border-radius:999px;'
|
||||
.'background:#ccfbf1;color:#0f766e;font-size:10px;font-weight:bold;cursor:pointer;vertical-align:middle;">'
|
||||
.'background:#ccfbf1;color:#0f766e;font-size:0.78em;font-weight:bold;cursor:pointer;vertical-align:middle;">'
|
||||
.'✅ 快速签到</button>';
|
||||
|
||||
return '【'.e($user->username).'】完成今日签到,连续签到 '
|
||||
|
||||
@@ -104,9 +104,10 @@ class EarnController extends Controller
|
||||
|
||||
// 6. 广播全服系统消息
|
||||
if ($roomId > 0) {
|
||||
// 公屏消息内的入口标签使用相对字号,跟随用户在聊天室选择的字号。
|
||||
$promoTag = ' <span onclick="window.dispatchEvent(new CustomEvent(\'open-earn-panel\'))" '
|
||||
.'style="display:inline-block;margin-left:6px;padding:1px 7px;background:#e9e4f5;'
|
||||
.'color:#6d4fa8;border-radius:10px;font-size:10px;cursor:pointer;font-weight:bold;vertical-align:middle;'
|
||||
.'color:#6d4fa8;border-radius:10px;font-size:0.78em;cursor:pointer;font-weight:bold;vertical-align:middle;'
|
||||
.'border:1px solid #d0c4ec;" title="点击赚金币">💰 看视频赚金币</span>';
|
||||
|
||||
$sysMsg = [
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace App\Http\Controllers;
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Models\GameConfig;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\FishingService;
|
||||
use App\Services\GameRoomScopeService;
|
||||
@@ -36,6 +37,9 @@ use Illuminate\Support\Str;
|
||||
*/
|
||||
class FishingController extends Controller
|
||||
{
|
||||
/**
|
||||
* 注入钓鱼流程需要的状态、会员、金币、商店和房间范围服务。
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly VipService $vipService,
|
||||
@@ -84,6 +88,14 @@ class FishingController extends Controller
|
||||
], 429);
|
||||
}
|
||||
|
||||
$tokenKey = "fishing:token:{$user->id}";
|
||||
if (Redis::exists($tokenKey)) {
|
||||
$activeSessionResponse = $this->restoreActiveFishingSessionResponse($user, $tokenKey);
|
||||
if ($activeSessionResponse) {
|
||||
return $activeSessionResponse;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 检查金币是否足够
|
||||
$cost = (int) (GameConfig::param('fishing', 'fishing_cost') ?? Sysparam::getValue('fishing_cost', '5'));
|
||||
if (($user->jjb ?? 0) < $cost) {
|
||||
@@ -93,34 +105,54 @@ class FishingController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 3. 扣除金币
|
||||
$this->currencyService->change(
|
||||
$user, 'gold', -$cost,
|
||||
CurrencySource::FISHING_COST,
|
||||
"钓鱼抛竿消耗 {$cost} 金币",
|
||||
$id,
|
||||
);
|
||||
$user->refresh();
|
||||
|
||||
// 4. 生成一次性 token,存入 Redis(TTL = 等待时间 + 收竿窗口 + 缓冲)
|
||||
// 3. 生成一次性 token,存入 Redis(TTL = 等待时间 + 收竿窗口 + 缓冲)
|
||||
$waitMin = (int) (GameConfig::param('fishing', 'fishing_wait_min') ?? Sysparam::getValue('fishing_wait_min', '8'));
|
||||
$waitMax = (int) (GameConfig::param('fishing', 'fishing_wait_max') ?? Sysparam::getValue('fishing_wait_max', '15'));
|
||||
$waitTime = rand($waitMin, $waitMax);
|
||||
$token = Str::random(32);
|
||||
$tokenKey = "fishing:token:{$user->id}";
|
||||
// token 有效期 = 等待时间 + 8秒点击窗口 + 5秒缓冲
|
||||
// 同时把 cast 时间戳和 wait_time 一起存入,供 reel 做服务端时间校验
|
||||
Redis::setex($tokenKey, $waitTime + 13, json_encode([
|
||||
$tokenTtl = $waitTime + 13;
|
||||
$tokenPayload = json_encode([
|
||||
'token' => $token,
|
||||
'cast_at' => time(),
|
||||
'wait_time' => $waitTime,
|
||||
]));
|
||||
]);
|
||||
|
||||
// 5. 生成随机浮漂坐标(百分比,避开边缘)
|
||||
// 原子占用本次抛竿 token,避免多标签页自动钓鱼互相覆盖令牌。
|
||||
$reserved = Redis::command('set', [$tokenKey, $tokenPayload, 'EX', $tokenTtl, 'NX']);
|
||||
if (! $reserved) {
|
||||
$activeSessionResponse = $this->restoreActiveFishingSessionResponse($user, $tokenKey);
|
||||
if ($activeSessionResponse) {
|
||||
return $activeSessionResponse;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => '钓鱼状态同步中,请稍后重试。',
|
||||
'retry_after' => 3,
|
||||
], 409);
|
||||
}
|
||||
|
||||
try {
|
||||
// token 占用成功后才扣金币,确保重复抛竿不会多扣费用。
|
||||
$this->currencyService->change(
|
||||
$user, 'gold', -$cost,
|
||||
CurrencySource::FISHING_COST,
|
||||
"钓鱼抛竿消耗 {$cost} 金币",
|
||||
$id,
|
||||
);
|
||||
$user->refresh();
|
||||
} catch (\Throwable $exception) {
|
||||
// 金币扣除失败时释放 token,避免用户被短时间卡在未收竿状态。
|
||||
Redis::del($tokenKey);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
|
||||
// 4. 生成随机浮漂坐标(百分比,避开边缘)
|
||||
$bobberX = rand(15, 85); // 左右 15%~85%
|
||||
$bobberY = rand(20, 65); // 上下 20%~65%
|
||||
|
||||
// 6. 检查是否持有有效自动钓鱼卡
|
||||
// 5. 检查是否持有有效自动钓鱼卡
|
||||
$autoFishingMinutes = $this->shopService->getActiveAutoFishingMinutesLeft($user);
|
||||
|
||||
return response()->json([
|
||||
@@ -137,6 +169,37 @@ class FishingController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复已有钓鱼会话,避免刷新页面后丢失前端内存里的收竿令牌。
|
||||
*/
|
||||
private function restoreActiveFishingSessionResponse(User $user, string $tokenKey): ?JsonResponse
|
||||
{
|
||||
$stored = json_decode((string) Redis::get($tokenKey), true);
|
||||
if (! is_array($stored) || empty($stored['token'])) {
|
||||
Redis::del($tokenKey);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$elapsed = time() - (int) ($stored['cast_at'] ?? 0);
|
||||
$waitTime = max(0, (int) ($stored['wait_time'] ?? 0) - $elapsed);
|
||||
$autoFishingMinutes = $this->shopService->getActiveAutoFishingMinutesLeft($user);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '已恢复正在进行的钓鱼,请等待本次收竿。',
|
||||
'wait_time' => $waitTime,
|
||||
'bobber_x' => rand(15, 85),
|
||||
'bobber_y' => rand(20, 65),
|
||||
'token' => (string) $stored['token'],
|
||||
'auto_fishing' => $autoFishingMinutes > 0,
|
||||
'auto_fishing_minutes_left' => $autoFishingMinutes,
|
||||
'cost' => 0,
|
||||
'jjb' => $user->jjb,
|
||||
'restored' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 收竿 — 验证浮漂 token,随机计算钓鱼结果,更新经验/金币,广播到聊天室。
|
||||
*
|
||||
|
||||
@@ -303,12 +303,13 @@ class UserController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存聊天室屏蔽与禁音偏好。
|
||||
* 保存聊天室屏蔽、禁音与字号偏好。
|
||||
*/
|
||||
public function updateChatPreferences(UpdateChatPreferencesRequest $request): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
$data = $request->validated();
|
||||
$existingPreferences = is_array($user->chat_preferences) ? $user->chat_preferences : [];
|
||||
$blockedSystemSenders = collect($data['blocked_system_senders'] ?? [])
|
||||
->map(function (string $sender): string {
|
||||
// 猜谜活动前端文案允许升级,但持久化键仍复用旧值,避免历史偏好失效。
|
||||
@@ -324,6 +325,15 @@ class UserController extends Controller
|
||||
'sound_muted' => (bool) $data['sound_muted'],
|
||||
];
|
||||
|
||||
// 字号偏好和屏蔽/禁音共用账号配置,旧请求未携带字号时保留原值。
|
||||
$fontSize = array_key_exists('font_size', $data) && $data['font_size'] !== null
|
||||
? (int) $data['font_size']
|
||||
: ($existingPreferences['font_size'] ?? null);
|
||||
|
||||
if ($fontSize !== null) {
|
||||
$preferences['font_size'] = (int) $fontSize;
|
||||
}
|
||||
|
||||
$user->update([
|
||||
'chat_preferences' => $preferences,
|
||||
]);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* 文件功能:聊天室偏好设置验证器
|
||||
* 负责校验用户提交的屏蔽播报与禁音配置。
|
||||
* 负责校验用户提交的屏蔽播报、禁音与聊天室字号配置。
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests;
|
||||
@@ -12,7 +12,7 @@ use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* 聊天室偏好设置验证器
|
||||
* 仅允许提交白名单内的屏蔽项与布尔型禁音状态。
|
||||
* 仅允许提交白名单内的屏蔽项、布尔型禁音状态与合法字号。
|
||||
*/
|
||||
class UpdateChatPreferencesRequest extends FormRequest
|
||||
{
|
||||
@@ -38,6 +38,7 @@ class UpdateChatPreferencesRequest extends FormRequest
|
||||
Rule::in(['钓鱼播报', '猜成语', '猜谜活动', '星海小博士', '百家乐', '跑马', '神秘箱子', '五子棋', '老虎机', '双色球彩票']),
|
||||
],
|
||||
'sound_muted' => ['required', 'boolean'],
|
||||
'font_size' => ['nullable', 'integer', 'min:10', 'max:30'],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -53,6 +54,9 @@ class UpdateChatPreferencesRequest extends FormRequest
|
||||
'blocked_system_senders.*.in' => '存在不支持的屏蔽项目。',
|
||||
'sound_muted.required' => '请传入禁音状态。',
|
||||
'sound_muted.boolean' => '禁音状态格式无效。',
|
||||
'font_size.integer' => '聊天室字号格式无效。',
|
||||
'font_size.min' => '聊天室字号不能小于 10。',
|
||||
'font_size.max' => '聊天室字号不能大于 30。',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +400,8 @@ class BaccaratLossCoverService
|
||||
}
|
||||
|
||||
if ($compensableCount > 0) {
|
||||
$button = '<button onclick="claimBaccaratLossCover('.$event->id.')" style="margin-left:8px;padding:3px 12px;background:#16a34a;color:#fff;border:none;border-radius:12px;cursor:pointer;font-size:12px;font-weight:bold;">领取补偿</button>';
|
||||
// 聊天消息内的按钮使用相对字号,跟随用户在底部工具栏选择的聊天字号。
|
||||
$button = '<button onclick="claimBaccaratLossCover('.$event->id.')" style="margin-left:8px;padding:3px 12px;background:#16a34a;color:#fff;border:none;border-radius:12px;cursor:pointer;font-size:0.82em;font-weight:bold;">领取补偿</button>';
|
||||
$content = "📣 【{$event->title}】活动已结束并完成结算!本次共有 <b>{$compensableCount}</b> 位玩家可领取补偿,截止时间:{$event->claim_deadline_at?->format('m-d H:i')}。{$button}";
|
||||
} else {
|
||||
$content = "📣 【{$event->title}】活动已结束!本次活动没有产生可领取补偿的记录。";
|
||||
@@ -446,7 +447,7 @@ class BaccaratLossCoverService
|
||||
|
||||
$formattedAmount = number_format($amount);
|
||||
$button = $event->status === 'claimable'
|
||||
? ' <button onclick="claimBaccaratLossCover('.$event->id.')" style="margin-left:8px;padding:3px 12px;background:#16a34a;color:#fff;border:none;border-radius:12px;cursor:pointer;font-size:12px;font-weight:bold;">领取补偿</button>'
|
||||
? ' <button onclick="claimBaccaratLossCover('.$event->id.')" style="margin-left:8px;padding:3px 12px;background:#16a34a;color:#fff;border:none;border-radius:12px;cursor:pointer;font-size:0.82em;font-weight:bold;">领取补偿</button>'
|
||||
: '';
|
||||
|
||||
// 领取成功的公屏格式复用百家乐参与播报风格,保证聊天室感知一致。
|
||||
|
||||
@@ -94,10 +94,11 @@ class FishingService
|
||||
$promoTag = '';
|
||||
if (! $isAi) {
|
||||
$autoFishingMinutesLeft = $this->shopService->getActiveAutoFishingMinutesLeft($user);
|
||||
// 公屏消息内的促销标签使用相对字号,避免覆盖用户在聊天室选择的字号。
|
||||
$promoTag = $autoFishingMinutesLeft > 0
|
||||
? ' <span onclick="window.openShopModal&&window.openShopModal()" '
|
||||
.'style="display:inline-block;margin-left:6px;padding:1px 7px;background:#e9e4f5;'
|
||||
.'color:#6d4fa8;border-radius:10px;font-size:10px;cursor:pointer;font-weight:bold;vertical-align:middle;'
|
||||
.'color:#6d4fa8;border-radius:10px;font-size:0.78em;cursor:pointer;font-weight:bold;vertical-align:middle;'
|
||||
.'border:1px solid #d0c4ec;" title="点击购买自动钓鱼卡">🎣 自动钓鱼卡</span>'
|
||||
: '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user