131 lines
4.7 KiB
PHP
131 lines
4.7 KiB
PHP
<?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 = 3;
|
||
|
||
/**
|
||
* @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' => "今日视频收益次数已达上限(每天最多{$this->maxDailyLimit}次),请明天再来。",
|
||
]);
|
||
}
|
||
|
||
// 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' => '#16a34a',
|
||
'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' => '',
|
||
]);
|
||
}
|
||
}
|