新增聊天室状态与功能快捷菜单
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天室用户状态变更广播事件
|
||||
* 负责在用户设置或清除当日状态后,实时同步当前房间在线名单展示。
|
||||
*/
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class UserStatusUpdated implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* 构造聊天室用户状态变更广播事件。
|
||||
*
|
||||
* @param int $roomId 房间 ID
|
||||
* @param string $username 状态变更用户昵称
|
||||
* @param array<string, mixed> $user 最新在线名单载荷
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly int $roomId,
|
||||
public readonly string $username,
|
||||
public readonly array $user,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 获取广播频道。
|
||||
*
|
||||
* @return array<int, \Illuminate\Broadcasting\Channel>
|
||||
*/
|
||||
public function broadcastOn(): array
|
||||
{
|
||||
return [
|
||||
new PresenceChannel('room.'.$this->roomId),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取广播数据。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function broadcastWith(): array
|
||||
{
|
||||
return [
|
||||
'username' => $this->username,
|
||||
'user' => $this->user,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\AiProviderConfig;
|
||||
use App\Models\Sysparam;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\ChatUserPresenceService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -33,6 +34,7 @@ class AiProviderController extends Controller
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly ChatUserPresenceService $chatUserPresenceService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -283,19 +285,8 @@ class AiProviderController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
$userData = [
|
||||
'user_id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'level' => $user->user_level,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $user->headface,
|
||||
'vip_icon' => $user->vipIcon(),
|
||||
'vip_name' => $user->vipName(),
|
||||
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
||||
'is_admin' => false,
|
||||
'position_icon' => '',
|
||||
'position_name' => '',
|
||||
];
|
||||
// 机器人在线载荷也统一走聊天室展示服务,避免名单字段口径逐步漂移。
|
||||
$userData = $this->chatUserPresenceService->build($user);
|
||||
|
||||
// 广播机器人进出事件(供前端名单增删)
|
||||
broadcast(new \App\Events\ChatBotToggled($userData, $isEnabled));
|
||||
|
||||
@@ -25,11 +25,13 @@ use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Services\AppointmentService;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\ChatUserPresenceService;
|
||||
use App\Services\MessageFilterService;
|
||||
use App\Services\PositionPermissionService;
|
||||
use App\Services\RoomBroadcastService;
|
||||
use App\Services\UserCurrencyService;
|
||||
use App\Services\VipService;
|
||||
use App\Support\ChatDailyStatusCatalog;
|
||||
use App\Support\PositionPermissionRegistry;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -54,6 +56,7 @@ class ChatController extends Controller
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly ChatUserPresenceService $chatUserPresenceService,
|
||||
private readonly MessageFilterService $filter,
|
||||
private readonly VipService $vipService,
|
||||
private readonly \App\Services\ShopService $shopService,
|
||||
@@ -105,21 +108,7 @@ class ChatController extends Controller
|
||||
}
|
||||
|
||||
// 2. 将当前用户加入到 Redis 房间在线列表(包含 VIP 和管理员信息)
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
// 获取当前在职职务信息(用于内容显示)
|
||||
$activePosition = $user->activePosition;
|
||||
$userData = [
|
||||
'user_id' => $user->id,
|
||||
'level' => $user->user_level,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $user->headface,
|
||||
'vip_icon' => $user->vipIcon(),
|
||||
'vip_name' => $user->vipName(),
|
||||
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
||||
'is_admin' => $user->user_level >= $superLevel,
|
||||
'position_icon' => $activePosition?->position?->icon ?? '',
|
||||
'position_name' => $activePosition?->position?->name ?? '',
|
||||
];
|
||||
$userData = $this->chatUserPresenceService->build($user);
|
||||
$this->chatState->userJoin($id, $user->username, $userData);
|
||||
// 记录重新加入房间的精确时间戳(微秒),用于防抖判断(刷新的时候避免闪退闪进播报)
|
||||
\Illuminate\Support\Facades\Redis::set("room:{$id}:join_time:{$user->username}", microtime(true));
|
||||
@@ -296,6 +285,8 @@ class ChatController extends Controller
|
||||
'pendingDivorce' => $pendingDivorceData,
|
||||
'roomPermissionMap' => $roomPermissionMap,
|
||||
'hasRoomManagementPermission' => in_array(true, $roomPermissionMap, true),
|
||||
'dailyStatusCatalog' => ChatDailyStatusCatalog::groupedOptions(),
|
||||
'activeDailyStatus' => $this->chatUserPresenceService->currentDailyStatus($user),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -526,18 +517,7 @@ class ChatController extends Controller
|
||||
|
||||
// 3. 将新的等级反馈给当前用户的在线名单上
|
||||
// 确保刚刚升级后别人查看到的也是最准确等级
|
||||
$activePosition = $user->activePosition;
|
||||
$this->chatState->userJoin($id, $user->username, [
|
||||
'level' => $user->user_level,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $user->headface,
|
||||
'vip_icon' => $user->vipIcon(),
|
||||
'vip_name' => $user->vipName(),
|
||||
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
||||
'is_admin' => $user->user_level >= $superLevel,
|
||||
'position_icon' => $activePosition?->position?->icon ?? '',
|
||||
'position_name' => $activePosition?->position?->name ?? '',
|
||||
]);
|
||||
$this->chatState->userJoin($id, $user->username, $this->chatUserPresenceService->build($user));
|
||||
|
||||
// 4. 如果突破境界,向全房系统喊话广播!
|
||||
if ($leveledUp) {
|
||||
@@ -806,18 +786,10 @@ class ChatController extends Controller
|
||||
|
||||
// 将新头像同步到 Redis 在线用户列表中(所有房间)
|
||||
// 通过更新 Redis 的用户信息,使得其他用户和自己刷新后都能看到新头像
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
$rooms = $this->chatState->getUserRooms($user->username);
|
||||
foreach ($rooms as $roomId) {
|
||||
$this->chatState->userJoin((int) $roomId, $user->username, [
|
||||
'level' => $user->user_level,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $headface,
|
||||
'vip_icon' => $user->vipIcon(),
|
||||
'vip_name' => $user->vipName(),
|
||||
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
||||
'is_admin' => $user->user_level >= $superLevel,
|
||||
]);
|
||||
// 头像更新后,统一通过在线载荷服务刷新所有扩展字段,避免状态或职务字段丢失。
|
||||
$this->chatState->userJoin((int) $roomId, $user->username, $this->chatUserPresenceService->build($user));
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
@@ -872,18 +844,10 @@ class ChatController extends Controller
|
||||
}
|
||||
|
||||
// 同步 Redis 状态
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
$rooms = $this->chatState->getUserRooms($user->username);
|
||||
foreach ($rooms as $roomId) {
|
||||
$this->chatState->userJoin((int) $roomId, $user->username, [
|
||||
'level' => $user->user_level,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $user->headface, // Use accessor
|
||||
'vip_icon' => $user->vipIcon(),
|
||||
'vip_name' => $user->vipName(),
|
||||
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
||||
'is_admin' => $user->user_level >= $superLevel,
|
||||
]);
|
||||
// 自定义头像上传成功后,同步覆盖在线名单中的全部展示字段。
|
||||
$this->chatState->userJoin((int) $roomId, $user->username, $this->chatUserPresenceService->build($user));
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
|
||||
@@ -19,20 +19,35 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Events\UserKicked;
|
||||
use App\Events\UserMuted;
|
||||
use App\Events\UserStatusUpdated;
|
||||
use App\Http\Requests\ChangePasswordRequest;
|
||||
use App\Http\Requests\UpdateChatPreferencesRequest;
|
||||
use App\Http\Requests\UpdateDailyStatusRequest;
|
||||
use App\Http\Requests\UpdateProfileRequest;
|
||||
use App\Models\Room;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\ChatUserPresenceService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
/**
|
||||
* 类功能:处理用户资料、聊天室偏好、当日状态与基础管理动作。
|
||||
*/
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* 构造用户控制器依赖。
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly ChatUserPresenceService $chatUserPresenceService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 查看其他用户资料片 (对应 USERinfo.ASP)
|
||||
*/
|
||||
@@ -230,6 +245,55 @@ class UserController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存聊天室当日状态,并同步当前在线名单显示。
|
||||
*/
|
||||
public function updateDailyStatus(UpdateDailyStatusRequest $request): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
$data = $request->validated();
|
||||
$roomId = (int) $data['room_id'];
|
||||
|
||||
// 仅允许当前确实在线的用户从聊天室内修改状态,避免离线脏请求写入。
|
||||
if (! $this->chatState->isUserInRoom($roomId, $user->username)) {
|
||||
return response()->json([
|
||||
'status' => 'error',
|
||||
'message' => '请先进入聊天室后再设置状态。',
|
||||
], 422);
|
||||
}
|
||||
|
||||
if ($data['action'] === 'clear') {
|
||||
$user->update([
|
||||
'daily_status_key' => null,
|
||||
'daily_status_expires_at' => null,
|
||||
]);
|
||||
} else {
|
||||
// 状态有效期固定维持到当天结束,次日自动失效。
|
||||
$user->update([
|
||||
'daily_status_key' => $data['status_key'],
|
||||
'daily_status_expires_at' => now()->endOfDay(),
|
||||
]);
|
||||
}
|
||||
|
||||
$user->refresh();
|
||||
$presencePayload = $this->chatUserPresenceService->build($user);
|
||||
$roomIds = $this->chatState->getUserRooms($user->username);
|
||||
|
||||
foreach ($roomIds as $activeRoomId) {
|
||||
// 所有当前在线房间都刷新 Redis 载荷,确保头像、会员与状态显示口径一致。
|
||||
$this->chatState->userJoin((int) $activeRoomId, $user->username, $presencePayload);
|
||||
broadcast(new UserStatusUpdated((int) $activeRoomId, $user->username, $presencePayload));
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => $data['action'] === 'clear' ? '状态已清除。' : '状态已更新。',
|
||||
'data' => [
|
||||
'status' => $this->chatUserPresenceService->currentDailyStatus($user),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码 (对应 chpasswd.asp)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天室用户状态设置验证器
|
||||
* 负责校验用户在聊天室内提交的当日状态设置与清除请求。
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Support\ChatDailyStatusCatalog;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateDailyStatusRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* 允许已登录用户保存自己的当日状态。
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天室当日状态设置的验证规则。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'room_id' => ['required', 'integer', 'exists:rooms,id'],
|
||||
'action' => ['required', 'string', Rule::in(['set', 'clear'])],
|
||||
'status_key' => [
|
||||
Rule::requiredIf(fn (): bool => $this->input('action') === 'set'),
|
||||
'nullable',
|
||||
'string',
|
||||
Rule::in(ChatDailyStatusCatalog::keys()),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天室当日状态设置的中文错误提示。
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'room_id.required' => '缺少当前房间信息。',
|
||||
'room_id.integer' => '房间编号格式无效。',
|
||||
'room_id.exists' => '当前房间不存在。',
|
||||
'action.required' => '缺少状态操作类型。',
|
||||
'action.in' => '不支持的状态操作类型。',
|
||||
'status_key.required' => '请选择要设置的状态。',
|
||||
'status_key.in' => '请选择系统支持的状态。',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,8 @@ class User extends Authenticatable
|
||||
'custom_join_effect',
|
||||
'custom_leave_effect',
|
||||
'chat_preferences',
|
||||
'daily_status_key',
|
||||
'daily_status_expires_at',
|
||||
'user_level',
|
||||
'inviter_id',
|
||||
'room_id',
|
||||
@@ -107,6 +109,7 @@ class User extends Authenticatable
|
||||
'q3_time' => 'datetime',
|
||||
'has_received_new_gift' => 'boolean',
|
||||
'chat_preferences' => 'array',
|
||||
'daily_status_expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天室在线用户展示数据服务
|
||||
* 负责统一拼装聊天室在线名单、Presence 频道与 Redis 在线状态使用的用户载荷。
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Support\ChatDailyStatusCatalog;
|
||||
|
||||
class ChatUserPresenceService
|
||||
{
|
||||
/**
|
||||
* 构建聊天室在线用户载荷。
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function build(User $user): array
|
||||
{
|
||||
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
||||
$activePosition = $user->activePosition;
|
||||
$position = $activePosition?->position;
|
||||
$payload = [
|
||||
'id' => $user->id,
|
||||
'user_id' => $user->id,
|
||||
'username' => $user->username,
|
||||
'level' => $user->user_level,
|
||||
'user_level' => $user->user_level,
|
||||
'sex' => $user->sex,
|
||||
'headface' => $user->headface,
|
||||
'headface_url' => $user->headfaceUrl,
|
||||
'headfaceUrl' => $user->headfaceUrl,
|
||||
'vip_icon' => $user->vipIcon(),
|
||||
'vip_name' => $user->vipName(),
|
||||
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
||||
'is_admin' => $user->user_level >= $superLevel,
|
||||
'position_icon' => $position?->icon ?? '',
|
||||
'position_name' => $position?->name ?? '',
|
||||
'department_name' => $position?->department?->name ?? '',
|
||||
];
|
||||
|
||||
$activeStatus = $this->currentDailyStatus($user);
|
||||
if ($activeStatus !== null) {
|
||||
$payload['daily_status_key'] = $activeStatus['key'];
|
||||
$payload['daily_status_label'] = $activeStatus['label'];
|
||||
$payload['daily_status_icon'] = $activeStatus['icon'];
|
||||
$payload['daily_status_group'] = $activeStatus['group'];
|
||||
$payload['daily_status_expires_at'] = $activeStatus['expires_at'];
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取用户当前仍然有效的当日状态。
|
||||
*
|
||||
* @return array{key: string, label: string, icon: string, group: string, expires_at: string}|null
|
||||
*/
|
||||
public function currentDailyStatus(User $user): ?array
|
||||
{
|
||||
return ChatDailyStatusCatalog::resolveActiveStatus(
|
||||
$user->daily_status_key,
|
||||
$user->daily_status_expires_at,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天室当日状态目录
|
||||
* 负责集中维护聊天室状态分组、文案、图标与激活状态解析逻辑。
|
||||
*/
|
||||
|
||||
namespace App\Support;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
|
||||
class ChatDailyStatusCatalog
|
||||
{
|
||||
/**
|
||||
* 固定状态目录。
|
||||
*
|
||||
* @return array<int, array{group: string, items: array<int, array{key: string, label: string, icon: string}>}>
|
||||
*/
|
||||
public static function groupedOptions(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'group' => '心情想法',
|
||||
'items' => [
|
||||
['key' => 'feeling_great', 'label' => '美滋滋', 'icon' => '🙂'],
|
||||
['key' => 'heartbroken', 'label' => '裂开', 'icon' => '💔'],
|
||||
['key' => 'lucky_fish', 'label' => '求锦鲤', 'icon' => '🐟'],
|
||||
['key' => 'waiting_sun', 'label' => '等天晴', 'icon' => '☀️'],
|
||||
['key' => 'tired', 'label' => '疲惫', 'icon' => '😮💨'],
|
||||
['key' => 'dazed', 'label' => '发呆', 'icon' => '💭'],
|
||||
['key' => 'charging', 'label' => '冲', 'icon' => '🚀'],
|
||||
['key' => 'emo', 'label' => 'emo', 'icon' => '🥀'],
|
||||
['key' => 'overthinking', 'label' => '胡思乱想', 'icon' => '☁️'],
|
||||
['key' => 'energetic', 'label' => '元气满满', 'icon' => '✨'],
|
||||
['key' => 'bot', 'label' => 'bot', 'icon' => '🤖'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'group' => '工作学习',
|
||||
'items' => [
|
||||
['key' => 'working_hard', 'label' => '搬砖', 'icon' => '🧱'],
|
||||
['key' => 'studying', 'label' => '沉迷学习', 'icon' => '📚'],
|
||||
['key' => 'busy', 'label' => '忙', 'icon' => '🙆'],
|
||||
['key' => 'slacking', 'label' => '摸鱼', 'icon' => '🐟'],
|
||||
['key' => 'business_trip', 'label' => '出差', 'icon' => '✈️'],
|
||||
['key' => 'running_home', 'label' => '飞奔回家', 'icon' => '🏃'],
|
||||
['key' => 'do_not_disturb', 'label' => '勿扰模式', 'icon' => '🌙'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'group' => '活动',
|
||||
'items' => [
|
||||
['key' => 'wandering', 'label' => '浪', 'icon' => '🌊'],
|
||||
['key' => 'clocking_in', 'label' => '打卡', 'icon' => '✌️'],
|
||||
['key' => 'exercising', 'label' => '运动', 'icon' => '🏃♂️'],
|
||||
['key' => 'coffee', 'label' => '喝咖啡', 'icon' => '☕'],
|
||||
['key' => 'milk_tea', 'label' => '喝奶茶', 'icon' => '🧋'],
|
||||
['key' => 'eating', 'label' => '干饭', 'icon' => '🍚'],
|
||||
['key' => 'parenting', 'label' => '带娃', 'icon' => '🧒'],
|
||||
['key' => 'save_world', 'label' => '拯救世界', 'icon' => '🦸'],
|
||||
['key' => 'selfie', 'label' => '自拍', 'icon' => '🤳'],
|
||||
],
|
||||
],
|
||||
[
|
||||
'group' => '休息',
|
||||
'items' => [
|
||||
['key' => 'retreat', 'label' => '闭关', 'icon' => '🧘'],
|
||||
['key' => 'staying_home', 'label' => '宅', 'icon' => '🛋️'],
|
||||
['key' => 'sleeping', 'label' => '睡觉', 'icon' => '💤'],
|
||||
['key' => 'cat_time', 'label' => '吸猫', 'icon' => '🐈'],
|
||||
['key' => 'walk_dog', 'label' => '遛狗', 'icon' => '🐕'],
|
||||
['key' => 'gaming', 'label' => '玩游戏', 'icon' => '🎮'],
|
||||
['key' => 'listening_music', 'label' => '听歌', 'icon' => '🎧'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回全部合法状态键。
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function keys(): array
|
||||
{
|
||||
return array_values(array_map(
|
||||
static fn (array $item): string => $item['key'],
|
||||
self::flatOptions()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据状态键查找对应配置。
|
||||
*
|
||||
* @return array{key: string, label: string, icon: string, group: string}|null
|
||||
*/
|
||||
public static function find(?string $key): ?array
|
||||
{
|
||||
if ($key === null || $key === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach (self::flatOptions() as $item) {
|
||||
if ($item['key'] === $key) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析当前仍处于有效期内的状态数据。
|
||||
*
|
||||
* @param CarbonInterface|string|null $expiresAt 到期时间
|
||||
* @return array{key: string, label: string, icon: string, group: string, expires_at: string}|null
|
||||
*/
|
||||
public static function resolveActiveStatus(?string $key, CarbonInterface|string|null $expiresAt): ?array
|
||||
{
|
||||
$option = self::find($key);
|
||||
if ($option === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$expiresAtCarbon = self::normalizeExpiresAt($expiresAt);
|
||||
if ($expiresAtCarbon === null || $expiresAtCarbon->isPast()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => $option['key'],
|
||||
'label' => $option['label'],
|
||||
'icon' => $option['icon'],
|
||||
'group' => $option['group'],
|
||||
'expires_at' => $expiresAtCarbon->toIso8601String(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将分组目录整理为扁平列表,便于校验与查找。
|
||||
*
|
||||
* @return array<int, array{key: string, label: string, icon: string, group: string}>
|
||||
*/
|
||||
private static function flatOptions(): array
|
||||
{
|
||||
$flat = [];
|
||||
|
||||
foreach (self::groupedOptions() as $group) {
|
||||
foreach ($group['items'] as $item) {
|
||||
$flat[] = [
|
||||
'key' => $item['key'],
|
||||
'label' => $item['label'],
|
||||
'icon' => $item['icon'],
|
||||
'group' => $group['group'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $flat;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规整状态到期时间为 Carbon 对象。
|
||||
*
|
||||
* @param CarbonInterface|string|null $expiresAt 原始到期时间
|
||||
*/
|
||||
private static function normalizeExpiresAt(CarbonInterface|string|null $expiresAt): ?CarbonInterface
|
||||
{
|
||||
if ($expiresAt instanceof CarbonInterface) {
|
||||
return $expiresAt;
|
||||
}
|
||||
|
||||
if ($expiresAt === null || $expiresAt === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Carbon::parse($expiresAt);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user