修复聊天室字号偏好和游戏通知显示

This commit is contained in:
pllx
2026-04-29 18:27:32 +08:00
parent 6748fbc44e
commit 50b050c4bc
18 changed files with 363 additions and 92 deletions
@@ -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).'】完成今日签到,连续签到 '
+2 -1
View File
@@ -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 = [
+80 -17
View File
@@ -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,随机计算钓鱼结果,更新经验/金币,广播到聊天室。
*
+11 -1
View File
@@ -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。',
];
}
}