signInService->status($user); return response()->json([ 'status' => 'success', 'data' => $this->formatStatusPayload($user, $status), ]); } /** * 方法功能:查询指定月份的签到日历与补签卡状态。 */ public function calendar(DailySignInCalendarRequest $request): JsonResponse { /** @var User $user */ $user = Auth::user(); return response()->json([ 'status' => 'success', 'data' => $this->signInService->calendar($user, $request->validated('month')), ]); } /** * 方法功能:领取今日签到奖励并同步聊天室在线名单。 */ public function claim(ClaimDailySignInRequest $request): JsonResponse { /** @var User $user */ $user = Auth::user(); $roomId = $request->validated('room_id'); $roomId = $roomId === null ? null : (int) $roomId; $dailySignIn = $this->signInService->claim($user, $roomId); if (! $dailySignIn->wasRecentlyCreated) { return response()->json([ 'status' => 'error', 'message' => '今日已签到,请明天再来。', 'data' => $this->formatClaimPayload($user->fresh(), $dailySignIn), ], 422); } $freshUser = $user->fresh(['vipLevel', 'activePosition.position.department']); $presencePayload = $this->presenceService->build($freshUser); $this->refreshOnlinePresence($freshUser, $presencePayload); if ($roomId !== null && $this->chatState->isUserInRoom($roomId, $freshUser->username)) { $this->broadcastSignInNotice($freshUser, $dailySignIn, $roomId); } return response()->json([ 'status' => 'success', 'message' => $this->buildSuccessMessage($dailySignIn), 'data' => $this->formatClaimPayload($freshUser, $dailySignIn, $presencePayload), ]); } /** * 方法功能:使用补签卡补签历史漏签日期。 */ public function makeup(MakeupDailySignInRequest $request): JsonResponse { /** @var User $user */ $user = Auth::user(); $roomId = $request->validated('room_id'); $roomId = $roomId === null ? null : (int) $roomId; $dailySignIn = $this->signInService->makeup($user, (string) $request->validated('target_date'), $roomId); $refreshedSignIn = $dailySignIn->fresh(); $latestSignIn = DailySignIn::query() ->where('user_id', $user->id) ->latest('sign_in_date') ->first(); $currentStreakDays = (int) ($latestSignIn?->streak_days ?? $refreshedSignIn?->streak_days ?? 0); $freshUser = $user->fresh(['vipLevel', 'activePosition.position.department']); $presencePayload = $this->presenceService->build($freshUser); $this->refreshOnlinePresence($freshUser, $presencePayload); if ($roomId !== null && $this->chatState->isUserInRoom($roomId, $freshUser->username)) { $this->broadcastSignInNotice($freshUser, $refreshedSignIn, $roomId, $currentStreakDays); } return response()->json([ 'status' => 'success', 'message' => '补签成功,'.$refreshedSignIn?->sign_in_date?->format('Y-m-d').' 已补签,当前连续签到 '.$currentStreakDays.' 天。', 'data' => $this->formatClaimPayload($freshUser, $refreshedSignIn, $presencePayload, $currentStreakDays), ]); } /** * 方法功能:刷新用户当前所在房间的 Redis 在线载荷并广播名单更新。 * * @param array $presencePayload */ private function refreshOnlinePresence(User $user, array $presencePayload): void { foreach ($this->chatState->getUserRooms($user->username) as $activeRoomId) { // 签到身份会展示在在线名单里,必须立即回写 Redis 载荷。 $this->chatState->userJoin((int) $activeRoomId, $user->username, $presencePayload); broadcast(new UserStatusUpdated((int) $activeRoomId, $user->username, $presencePayload)); } } /** * 方法功能:向当前聊天室广播签到成功通知。 */ private function broadcastSignInNotice(User $user, DailySignIn $dailySignIn, int $roomId, ?int $currentStreakDays = null): void { $message = [ 'id' => $this->chatState->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => '系统传音', 'to_user' => '大家', 'content' => $this->buildNoticeContent($user, $dailySignIn, $currentStreakDays), 'is_secret' => false, 'font_color' => '#0f766e', 'action' => '', 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage($roomId, $message); broadcast(new MessageSent($roomId, $message)); } /** * 方法功能:生成聊天室内的签到播报内容。 */ private function buildNoticeContent(User $user, DailySignIn $dailySignIn, ?int $currentStreakDays = null): string { $rewardText = $this->buildRewardText($dailySignIn); $identityText = $dailySignIn->identity_badge_name ? ',获得身份 '.e($dailySignIn->identity_badge_name) : ''; if ($dailySignIn->is_makeup) { $signInDate = $dailySignIn->sign_in_date?->format('Y-m-d') ?? '漏签日期'; $streakDays = $currentStreakDays ?? (int) $dailySignIn->streak_days; return '【'.e($user->username).'】使用补签卡补签 '.$signInDate .',当前连续签到 '.$streakDays.' 天,获得 '.$rewardText.$identityText.'。'; } // 聊天消息内的快捷按钮使用相对字号,避免覆盖用户选择的消息字号。 $quickButton = ''; return '【'.e($user->username).'】完成今日签到,连续签到 ' .$dailySignIn->streak_days.' 天,获得 '.$rewardText.$identityText.'。'.$quickButton; } /** * 方法功能:生成本机签到成功提示文案。 */ private function buildSuccessMessage(DailySignIn $dailySignIn): string { return '签到成功,连续签到 '.$dailySignIn->streak_days.' 天,获得 '.$this->buildRewardText($dailySignIn).'。'; } /** * 方法功能:按实际签到奖励快照生成奖励描述。 */ private function buildRewardText(DailySignIn $dailySignIn): string { $items = []; if ($dailySignIn->gold_reward > 0) { $items[] = $dailySignIn->gold_reward.' 金币'; } if ($dailySignIn->exp_reward > 0) { $items[] = $dailySignIn->exp_reward.' 经验'; } if ($dailySignIn->charm_reward > 0) { $items[] = $dailySignIn->charm_reward.' 魅力'; } return $items === [] ? '签到记录' : implode(' + ', $items); } /** * 方法功能:格式化状态查询响应载荷。 * * @param array $status * @return array */ private function formatStatusPayload(User $user, array $status): array { return [ 'signed_today' => $status['signed_today'], 'can_claim' => $status['can_claim'], 'current_streak_days' => $status['current_streak_days'], 'claimable_streak_days' => $status['claimable_streak_days'], 'preview_rule' => $status['matched_rule']?->only([ 'streak_days', 'gold_reward', 'exp_reward', 'charm_reward', 'identity_badge_name', 'identity_badge_icon', 'identity_badge_color', 'identity_duration_days', ]), 'identity' => $this->formatIdentityPayload($status['current_identity']), 'user' => [ 'jjb' => (int) $user->jjb, ], ]; } /** * 方法功能:格式化签到领取响应载荷。 * * @param array|null $presencePayload * @return array */ private function formatClaimPayload(User $user, DailySignIn $dailySignIn, ?array $presencePayload = null, ?int $currentStreakDays = null): array { $identity = $user->currentSignInIdentity(); return [ 'sign_in' => [ 'id' => $dailySignIn->id, 'sign_in_date' => $dailySignIn->sign_in_date?->toDateString(), 'is_makeup' => (bool) $dailySignIn->is_makeup, 'streak_days' => (int) $dailySignIn->streak_days, 'gold_reward' => (int) $dailySignIn->gold_reward, 'exp_reward' => (int) $dailySignIn->exp_reward, 'charm_reward' => (int) $dailySignIn->charm_reward, ], 'current_streak_days' => $currentStreakDays ?? (int) $dailySignIn->streak_days, 'identity' => $this->formatIdentityPayload($identity), 'presence' => $presencePayload ?? $this->presenceService->build($user), 'user' => [ 'jjb' => (int) $user->jjb, 'gold' => (int) $user->jjb, ], ]; } /** * 方法功能:格式化签到身份数据供前端展示。 * * @return array|null */ private function formatIdentityPayload(?UserIdentityBadge $identity): ?array { if ($identity === null) { return null; } return [ 'key' => $identity->badge_code, 'label' => $identity->badge_name, 'name' => $identity->badge_name, 'icon' => $identity->badge_icon ?? '✅', 'color' => $identity->badge_color ?? '#0f766e', 'expires_at' => $identity->expires_at?->toIso8601String(), 'streak_days' => (int) data_get($identity->metadata, 'streak_days', 0), ]; } }