306 lines
11 KiB
PHP
306 lines
11 KiB
PHP
<?php
|
|
|
|
/**
|
|
* 文件功能:前台每日签到控制器
|
|
*
|
|
* 提供签到状态查询、领取奖励、刷新在线名单载荷和聊天室签到通知。
|
|
*/
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Events\MessageSent;
|
|
use App\Events\UserStatusUpdated;
|
|
use App\Http\Requests\ClaimDailySignInRequest;
|
|
use App\Http\Requests\DailySignInCalendarRequest;
|
|
use App\Http\Requests\MakeupDailySignInRequest;
|
|
use App\Models\DailySignIn;
|
|
use App\Models\User;
|
|
use App\Models\UserIdentityBadge;
|
|
use App\Services\ChatStateService;
|
|
use App\Services\ChatUserPresenceService;
|
|
use App\Services\SignInService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
/**
|
|
* 类功能:处理前台用户每日签到状态与领取奖励流程。
|
|
*/
|
|
class DailySignInController extends Controller
|
|
{
|
|
/**
|
|
* 构造每日签到控制器依赖。
|
|
*/
|
|
public function __construct(
|
|
private readonly SignInService $signInService,
|
|
private readonly ChatStateService $chatState,
|
|
private readonly ChatUserPresenceService $presenceService,
|
|
) {}
|
|
|
|
/**
|
|
* 方法功能:查询当前用户今日签到状态和奖励预览。
|
|
*/
|
|
public function status(): JsonResponse
|
|
{
|
|
/** @var User $user */
|
|
$user = Auth::user();
|
|
$status = $this->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<string, mixed> $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 = '<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;">'
|
|
.'✅ 快速签到</button>';
|
|
|
|
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<string, mixed> $status
|
|
* @return array<string, mixed>
|
|
*/
|
|
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<string, mixed>|null $presencePayload
|
|
* @return array<string, mixed>
|
|
*/
|
|
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<string, mixed>|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),
|
|
];
|
|
}
|
|
}
|