Files
chatroom/app/Http/Controllers/EarnController.php
lkddi 97e32572cf feat: 新增看视频赚金币功能
- 在右侧导航新增「赚钱」入口(娱乐下方)
- 新增 earn-panel 弹窗:风格与商店一致,800px 宽度
- 集成 FluidPlayer + VAST 广告(ExoClick)
- 动态倒计时:实时监听视频 duration/currentTime
- VAST 失败时自动回退保底视频,20s 超时保底放行
- 修复 AbortError:idle 时 video 不预播放,仅提供 fallback source
- 删除不支持的 player.on('error') 调用
- 所有 overlay 改用绝对定位居中,修复 Alpine x-show 破坏 flex 问题
- EarnController:Redis 每日 10 次限额 + 冷却防刷
- 领取成功后广播全服系统消息(含金币+经验+快捷入口标签)
- 移除神秘盒子相关 UI 代码
2026-04-02 18:35:54 +08:00

131 lines
4.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Http\Controllers;
use App\Models\LevelCache;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redis;
/**
* 文件功能:用户赚取金币与经验(观看视频广告等任务)控制器
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
class EarnController extends Controller
{
public function __construct(
private readonly \App\Services\ChatStateService $chatState
) {}
/**
* @var int 每日观看最大次数限制
*/
private int $maxDailyLimit = 10;
/**
* @var int 每次领奖后至少需要的冷却时间(秒)
*/
private int $cooldownSeconds = 5;
/**
* 申领看视频的奖励
* 成功看完视频后前端发起此请求。
* 为防止刷包,必须加上每日总次数及短时频率限制。
*/
public function claimVideoReward(Request $request): JsonResponse
{
/** @var User $user */
$user = Auth::user();
$userId = $user->id;
$dateKey = now()->format('Y-m-d');
$dailyCountKey = "earn_video:count:{$userId}:{$dateKey}";
$cooldownKey = "earn_video:cooldown:{$userId}";
// 1. 检查冷却时间
if (Redis::exists($cooldownKey)) {
return response()->json([
'success' => false,
'message' => '操作过快,请稍后再试。',
]);
}
// 2. 检查每日最大次数
$todayCount = (int) Redis::get($dailyCountKey);
if ($todayCount >= $this->maxDailyLimit) {
return response()->json([
'success' => false,
'message' => '今日视频收益次数已达上限每天最多10次请明天再来。',
]);
}
// 3. 开始发放奖励并增加次数
// 增量前可能需要锁机制,但简单的 incr 在并发也不容易超量很多,且有限流
$newCount = Redis::incr($dailyCountKey);
// 设置每日次数键在同一天结束时过期,留一点余量
if ($newCount === 1) {
Redis::expire($dailyCountKey, 86400 * 2);
}
// 配置:单次 5000 金币500 经验
$rewardCoins = 5000;
$rewardExp = 500;
$user->increment('jjb', $rewardCoins);
$user->increment('exp_num', $rewardExp);
$user->refresh(); // 刷新模型以获取 increment 后的最新字段值
// 设置冷却时间
Redis::setex($cooldownKey, $this->cooldownSeconds, 1);
// 4. 检查是否升级
$levelUp = false;
$newLevelName = '';
// 我们利用现有的 levelCache 或者根据 exp_num 和当前 user_level 判断
// 因为这是一个常见的游戏房逻辑,通常判断用户当前经验是否大于下一级的经验要求
// 简化起见,如果需要严格触发升级系统,可参考已有的升华逻辑。
// (在此处简化为:只要给了经验,前端自然显示就好。部分框架逻辑可能不需要在此检查升降,而是下一次进入发言时自动算)
$roomId = (int) $request->input('room_id', 0);
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;'
.'border:1px solid #d0c4ec;" title="点击赚金币">💰 看视频赚金币</span>';
$sysMsg = [
'id' => $this->chatState->nextMessageId($roomId),
'room_id' => $roomId,
'from_user' => '金币任务',
'to_user' => '大家',
'content' => "👍 【{$user->username}】刚刚看视频赚取了 {$rewardCoins} 金币 + {$rewardExp} 经验!{$promoTag}",
'is_secret' => false,
'font_color' => '#d97706',
'action' => '',
'sent_at' => now()->toDateTimeString(),
];
$this->chatState->pushMessage($roomId, $sysMsg);
broadcast(new \App\Events\MessageSent($roomId, $sysMsg));
}
$remainingToday = $this->maxDailyLimit - $newCount;
return response()->json([
'success' => true,
'message' => "观看完毕!获得 {$rewardCoins} 金币 + {$rewardExp} 经验。今日还可观看 {$remainingToday} 次。",
'new_jjb' => $user->jjb, // refresh 后的真实值
'level_up' => false,
'new_level_name' => '',
]);
}
}