diff --git a/app/Http/Controllers/RedPacketController.php b/app/Http/Controllers/RedPacketController.php index b10e055..e4c7843 100644 --- a/app/Http/Controllers/RedPacketController.php +++ b/app/Http/Controllers/RedPacketController.php @@ -10,6 +10,7 @@ * 接入 UserCurrencyService 记录所有货币变动流水。 * * @author ChatRoom Laravel + * * @version 1.0.0 */ @@ -40,7 +41,7 @@ class RedPacketController extends Controller private const TOTAL_COUNT = 10; /** 礼包有效期(秒) */ - private const EXPIRE_SECONDS = 120; + private const EXPIRE_SECONDS = 300; /** * 构造函数:注入依赖服务 @@ -62,12 +63,12 @@ class RedPacketController extends Controller { $request->validate([ 'room_id' => 'required|integer', - 'type' => 'required|in:gold,exp', + 'type' => 'required|in:gold,exp', ]); - $user = Auth::user(); + $user = Auth::user(); $roomId = (int) $request->input('room_id'); - $type = $request->input('type'); // 'gold' 或 'exp' + $type = $request->input('type'); // 'gold' 或 'exp' // 权限校验:仅 superlevel 可发礼包 $superLevel = (int) Sysparam::getValue('superlevel', '100'); @@ -92,8 +93,8 @@ class RedPacketController extends Controller // 货币展示文案 $typeLabel = $type === 'exp' ? '经验' : '金币'; - $typeIcon = $type === 'exp' ? '✨' : '💰'; - $btnBg = $type === 'exp' + $typeIcon = $type === 'exp' ? '✨' : '💰'; + $btnBg = $type === 'exp' ? 'linear-gradient(135deg,#7c3aed,#4f46e5)' : 'linear-gradient(135deg,#dc2626,#ea580c)'; @@ -101,16 +102,16 @@ class RedPacketController extends Controller $envelope = DB::transaction(function () use ($user, $roomId, $type, $amounts): RedPacketEnvelope { // 创建红包主记录(凭空发出,不扣发包人货币) $envelope = RedPacketEnvelope::create([ - 'sender_id' => $user->id, + 'sender_id' => $user->id, 'sender_username' => $user->username, - 'room_id' => $roomId, - 'type' => $type, - 'total_amount' => self::TOTAL_AMOUNT, - 'total_count' => self::TOTAL_COUNT, - 'claimed_count' => 0, - 'claimed_amount' => 0, - 'status' => 'active', - 'expires_at' => now()->addSeconds(self::EXPIRE_SECONDS), + 'room_id' => $roomId, + 'type' => $type, + 'total_amount' => self::TOTAL_AMOUNT, + 'total_count' => self::TOTAL_COUNT, + 'claimed_count' => 0, + 'claimed_amount' => 0, + 'status' => 'active', + 'expires_at' => now()->addSeconds(self::EXPIRE_SECONDS), ]); // 将拆分好的数量序列存入 Redis(List,LPOP 抢红包) @@ -137,15 +138,15 @@ class RedPacketController extends Controller .'box-shadow:0 2px 6px rgba(0,0,0,0.3);">'.$typeIcon.' 立即抢包'; $msg = [ - 'id' => $this->chatState->nextMessageId($roomId), - 'room_id' => $roomId, - 'from_user' => '系统公告', - 'to_user' => '', - 'content' => "🧧 {$user->username} 发出了一个 ".self::TOTAL_AMOUNT." {$typeLabel}的礼包!共 ".self::TOTAL_COUNT." 份,先到先得,快去抢!{$btnHtml}", - 'is_secret' => false, + 'id' => $this->chatState->nextMessageId($roomId), + 'room_id' => $roomId, + 'from_user' => '系统公告', + 'to_user' => '', + 'content' => "🧧 {$user->username} 发出了一个 ".self::TOTAL_AMOUNT." {$typeLabel}的礼包!共 ".self::TOTAL_COUNT." 份,先到先得,快去抢!{$btnHtml}", + 'is_secret' => false, 'font_color' => $type === 'exp' ? '#6d28d9' : '#b91c1c', - 'action' => '', - 'sent_at' => now()->toDateTimeString(), + 'action' => '', + 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage($roomId, $msg); broadcast(new MessageSent($roomId, $msg)); @@ -153,18 +154,18 @@ class RedPacketController extends Controller // 广播红包事件(触发前端弹出红包卡片) broadcast(new RedPacketSent( - roomId: $roomId, - envelopeId: $envelope->id, + roomId: $roomId, + envelopeId: $envelope->id, senderUsername: $user->username, - totalAmount: self::TOTAL_AMOUNT, - totalCount: self::TOTAL_COUNT, - expireSeconds: self::EXPIRE_SECONDS, - type: $type, + totalAmount: self::TOTAL_AMOUNT, + totalCount: self::TOTAL_COUNT, + expireSeconds: self::EXPIRE_SECONDS, + type: $type, )); return response()->json([ - 'status' => 'success', - 'message' => "🧧 {$typeLabel}礼包已发出!".self::TOTAL_AMOUNT." {$typeLabel} · ".self::TOTAL_COUNT." 份", + 'status' => 'success', + 'message' => "🧧 {$typeLabel}礼包已发出!".self::TOTAL_AMOUNT." {$typeLabel} · ".self::TOTAL_COUNT.' 份', ]); } @@ -182,21 +183,26 @@ class RedPacketController extends Controller return response()->json(['status' => 'error', 'message' => '红包不存在'], 404); } - $user = Auth::user(); - $isExpired = $envelope->expires_at->isPast(); + $user = Auth::user(); + $isExpired = $envelope->expires_at->isPast(); $remainingCount = $envelope->remainingCount(); - $hasClaimed = RedPacketClaim::where('envelope_id', $envelopeId) + $hasClaimed = RedPacketClaim::where('envelope_id', $envelopeId) ->where('user_id', $user->id) ->exists(); + // 若已过期但 status 尚未同步,顺手更新为 expired + if ($isExpired && $envelope->status === 'active') { + $envelope->update(['status' => 'expired']); + } + return response()->json([ - 'status' => 'success', + 'status' => 'success', 'remaining_count' => $remainingCount, - 'total_count' => $envelope->total_count, - 'envelope_status' => $envelope->status, - 'is_expired' => $isExpired, - 'has_claimed' => $hasClaimed, - 'type' => $envelope->type ?? 'gold', + 'total_count' => $envelope->total_count, + 'envelope_status' => $isExpired ? 'expired' : $envelope->status, + 'is_expired' => $isExpired, + 'has_claimed' => $hasClaimed, + 'type' => $envelope->type ?? 'gold', ]); } @@ -207,8 +213,8 @@ class RedPacketController extends Controller * 重复领取通过 unique 约束保障幂等性。 * 按红包 type 字段决定入账金币还是经验。 * - * @param Request $request 需包含 room_id - * @param int $envelopeId 红包 ID + * @param Request $request 需包含 room_id + * @param int $envelopeId 红包 ID */ public function claim(Request $request, int $envelopeId): JsonResponse { @@ -216,7 +222,7 @@ class RedPacketController extends Controller 'room_id' => 'required|integer', ]); - $user = Auth::user(); + $user = Auth::user(); $roomId = (int) $request->input('room_id'); // 加载红包记录 @@ -240,11 +246,11 @@ class RedPacketController extends Controller // 从 Redis 原子 POP 一份数量 $redisKey = "red_packet:{$envelopeId}:amounts"; - $amount = \Illuminate\Support\Facades\Redis::lpop($redisKey); + $amount = \Illuminate\Support\Facades\Redis::lpop($redisKey); if ($amount === null || $amount === false) { return response()->json(['status' => 'error', 'message' => '礼包已被抢完!'], 422); } - $amount = (int) $amount; + $amount = (int) $amount; // 兼容旧记录(type 字段可能为 null) $envelopeType = $envelope->type ?? 'gold'; @@ -254,10 +260,10 @@ class RedPacketController extends Controller // 写领取记录(unique 约束保障不重复) RedPacketClaim::create([ 'envelope_id' => $envelope->id, - 'user_id' => $user->id, - 'username' => $user->username, - 'amount' => $amount, - 'claimed_at' => now(), + 'user_id' => $user->id, + 'username' => $user->username, + 'amount' => $amount, + 'claimed_at' => now(), ]); // 更新红包统计 @@ -302,30 +308,30 @@ class RedPacketController extends Controller broadcast(new RedPacketClaimed($user, $amount, $envelope->id)); // 在聊天室发送领取播报(所有人可见) - $typeLabel = $envelopeType === 'exp' ? '经验' : '金币'; - $typeIcon = $envelopeType === 'exp' ? '✨' : '💰'; + $typeLabel = $envelopeType === 'exp' ? '经验' : '金币'; + $typeIcon = $envelopeType === 'exp' ? '✨' : '💰'; $claimedMsg = [ - 'id' => $this->chatState->nextMessageId($roomId), - 'room_id' => $roomId, - 'from_user' => '系统传音', - 'to_user' => '', - 'content' => "🧧 {$user->username} 抢到了 {$amount} {$typeLabel}礼包!{$typeIcon}", - 'is_secret' => false, + 'id' => $this->chatState->nextMessageId($roomId), + 'room_id' => $roomId, + 'from_user' => '系统传音', + 'to_user' => '', + 'content' => "🧧 {$user->username} 抢到了 {$amount} {$typeLabel}礼包!{$typeIcon}", + 'is_secret' => false, 'font_color' => $envelopeType === 'exp' ? '#6d28d9' : '#d97706', - 'action' => '', - 'sent_at' => now()->toDateTimeString(), + 'action' => '', + 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage($roomId, $claimedMsg); broadcast(new MessageSent($roomId, $claimedMsg)); SaveMessageJob::dispatch($claimedMsg); $balanceField = $envelopeType === 'exp' ? 'exp_num' : 'jjb'; - $balanceNow = $user->fresh()->$balanceField; + $balanceNow = $user->fresh()->$balanceField; return response()->json([ - 'status' => 'success', - 'amount' => $amount, - 'type' => $envelopeType, + 'status' => 'success', + 'amount' => $amount, + 'type' => $envelopeType, 'message' => "🧧 恭喜!您抢到了 {$amount} {$typeLabel}!当前{$typeLabel}:{$balanceNow}。", ]); } @@ -338,18 +344,18 @@ class RedPacketController extends Controller * * @param int $total 总数量 * @param int $count 份数 - * @return int[] 每份数量数组 + * @return int[] 每份数量数组 */ private function splitAmount(int $total, int $count): array { - $amounts = []; + $amounts = []; $remaining = $total; for ($i = 1; $i < $count; $i++) { $leftCount = $count - $i; - $max = min((int) floor($remaining / $leftCount * 2), $remaining - $leftCount); - $max = max(1, $max); - $amount = random_int(1, $max); + $max = min((int) floor($remaining / $leftCount * 2), $remaining - $leftCount); + $max = max(1, $max); + $amount = random_int(1, $max); $amounts[] = $amount; $remaining -= $amount; } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 9cb0687..787856f 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -84,10 +84,11 @@ class UserController extends Controller $levelBanIp = (int) Sysparam::getValue('level_banip', '15'); if ($operator && $operator->user_level >= $levelBanIp) { $data['last_ip'] = $targetUser->last_ip; - $data['login_ip'] = $targetUser->login_ip; // 假设表中存在 login_ip 记录本次IP,若无则使用 last_ip 退化 + // last_ip 在每次登录时更新,即为用户最近一次登录的 IP(本次IP) + $data['login_ip'] = $targetUser->last_ip; // 解析归属地:使用 ip2region 离线库,直接返回原生中文(省|市|ISP) - $ipToLookup = $targetUser->login_ip ?: $targetUser->last_ip; + $ipToLookup = $targetUser->last_ip; if ($ipToLookup) { try { // 不传路径,使用 zoujingli/ip2region 包自带的内置数据库 diff --git a/resources/views/chat/frame.blade.php b/resources/views/chat/frame.blade.php index 231d78e..f17e15b 100644 --- a/resources/views/chat/frame.blade.php +++ b/resources/views/chat/frame.blade.php @@ -94,8 +94,8 @@ {{-- ═══════════ 左侧主区域 ═══════════ --}}