2026-02-26 13:35:38 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件功能:聊天室核心控制器
|
|
|
|
|
|
* 接管原版 INIT.ASP, NEWSAY.ASP, LEAVE.ASP 的所有职责
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author ChatRoom Laravel
|
|
|
|
|
|
*
|
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Events\MessageSent;
|
|
|
|
|
|
use App\Events\UserJoined;
|
|
|
|
|
|
use App\Events\UserLeft;
|
|
|
|
|
|
use App\Http\Requests\SendMessageRequest;
|
|
|
|
|
|
use App\Jobs\SaveMessageJob;
|
2026-02-26 21:10:34 +08:00
|
|
|
|
use App\Models\Autoact;
|
2026-02-26 13:35:38 +08:00
|
|
|
|
use App\Models\Room;
|
2026-02-26 21:10:34 +08:00
|
|
|
|
use App\Models\Sysparam;
|
2026-02-26 13:35:38 +08:00
|
|
|
|
use App\Services\ChatStateService;
|
|
|
|
|
|
use App\Services\MessageFilterService;
|
2026-02-26 21:30:07 +08:00
|
|
|
|
use App\Services\VipService;
|
2026-02-26 13:35:38 +08:00
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
2026-02-26 21:10:34 +08:00
|
|
|
|
use Illuminate\Support\Facades\Redis;
|
2026-02-26 13:35:38 +08:00
|
|
|
|
use Illuminate\View\View;
|
|
|
|
|
|
|
|
|
|
|
|
class ChatController extends Controller
|
|
|
|
|
|
{
|
|
|
|
|
|
public function __construct(
|
|
|
|
|
|
private readonly ChatStateService $chatState,
|
|
|
|
|
|
private readonly MessageFilterService $filter,
|
2026-02-26 21:30:07 +08:00
|
|
|
|
private readonly VipService $vipService,
|
2026-02-26 13:35:38 +08:00
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 进入房间初始化 (等同于原版 INIT.ASP)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $id 房间ID
|
|
|
|
|
|
* @return View|JsonResponse
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function init(int $id)
|
|
|
|
|
|
{
|
|
|
|
|
|
$room = Room::findOrFail($id);
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
// 房间人气 +1(每次访问递增,复刻原版人气计数)
|
|
|
|
|
|
$room->increment('visit_num');
|
|
|
|
|
|
|
2026-02-26 21:30:07 +08:00
|
|
|
|
// 1. 将当前用户加入到 Redis 房间在线列表(包含 VIP 和管理员信息)
|
|
|
|
|
|
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
|
|
|
|
|
$userData = [
|
2026-02-26 13:35:38 +08:00
|
|
|
|
'level' => $user->user_level,
|
|
|
|
|
|
'sex' => $user->sex,
|
|
|
|
|
|
'headface' => $user->headface,
|
2026-02-26 21:30:07 +08:00
|
|
|
|
'vip_icon' => $user->vipIcon(),
|
|
|
|
|
|
'vip_name' => $user->vipName(),
|
|
|
|
|
|
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
|
|
|
|
|
'is_admin' => $user->user_level >= $superLevel,
|
|
|
|
|
|
];
|
|
|
|
|
|
$this->chatState->userJoin($id, $user->username, $userData);
|
2026-02-26 13:35:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 广播 UserJoined 事件,通知房间内的其他人
|
2026-02-26 21:30:07 +08:00
|
|
|
|
broadcast(new UserJoined($id, $user->username, $userData))->toOthers();
|
2026-02-26 13:35:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 3. 获取历史消息用于初次渲染
|
|
|
|
|
|
// TODO: 可在前端通过请求另外的接口拉取历史记录,或者直接在这里 attach
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染主聊天框架视图
|
|
|
|
|
|
return view('chat.frame', [
|
|
|
|
|
|
'room' => $room,
|
|
|
|
|
|
'user' => $user,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 发送消息 (等同于原版 NEWSAY.ASP)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $id 房间ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function send(SendMessageRequest $request, int $id): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$data = $request->validated();
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
// 0. 检查用户是否被禁言(Redis TTL 自动过期)
|
|
|
|
|
|
$muteKey = "mute:{$id}:{$user->username}";
|
|
|
|
|
|
if (Redis::exists($muteKey)) {
|
|
|
|
|
|
$ttl = Redis::ttl($muteKey);
|
|
|
|
|
|
$minutes = ceil($ttl / 60);
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
|
'status' => 'error',
|
|
|
|
|
|
'message' => "您正在禁言中,还需等待约 {$minutes} 分钟。",
|
|
|
|
|
|
], 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 13:35:38 +08:00
|
|
|
|
// 1. 过滤净化消息体
|
|
|
|
|
|
$pureContent = $this->filter->filter($data['content'] ?? '');
|
|
|
|
|
|
if (empty($pureContent)) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => '消息内容不能为空或不合法。'], 422);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 封装消息对象
|
|
|
|
|
|
$messageData = [
|
|
|
|
|
|
'id' => $this->chatState->nextMessageId($id), // 分布式安全自增序号
|
|
|
|
|
|
'room_id' => $id,
|
|
|
|
|
|
'from_user' => $user->username,
|
|
|
|
|
|
'to_user' => $data['to_user'] ?? '大家',
|
|
|
|
|
|
'content' => $pureContent,
|
|
|
|
|
|
'is_secret' => $data['is_secret'] ?? false,
|
|
|
|
|
|
'font_color' => $data['font_color'] ?? '',
|
|
|
|
|
|
'action' => $data['action'] ?? '',
|
|
|
|
|
|
'sent_at' => now()->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 压入 Redis 缓存列表 (防炸内存,只保留最近 N 条)
|
|
|
|
|
|
$this->chatState->pushMessage($id, $messageData);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 立刻向 WebSocket 发射广播,前端达到 0 延迟渲染
|
|
|
|
|
|
broadcast(new MessageSent($id, $messageData));
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 丢进异步列队,慢慢持久化到 MySQL,保护数据库连接池
|
|
|
|
|
|
SaveMessageJob::dispatch($messageData);
|
|
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
// 6. 如果用户更换了字体颜色,顺便保存到 s_color 字段,下次进入时恢复
|
|
|
|
|
|
$chosenColor = $data['font_color'] ?? '';
|
|
|
|
|
|
if ($chosenColor && $chosenColor !== ($user->s_color ?? '')) {
|
|
|
|
|
|
$user->s_color = $chosenColor;
|
|
|
|
|
|
$user->save();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 13:35:38 +08:00
|
|
|
|
return response()->json(['status' => 'success']);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 自动挂机存点心跳与经验升级 (新增)
|
|
|
|
|
|
* 替代原版定时 iframe 刷新的 save.asp。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $id 房间ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function heartbeat(Request $request, int $id): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
if (! $user) {
|
|
|
|
|
|
return response()->json(['status' => 'error'], 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-27 00:44:45 +08:00
|
|
|
|
// 1. 心跳奖励:通过 Redis 限制最小间隔(默认30秒),防止频繁点击
|
|
|
|
|
|
$cooldownKey = "heartbeat_exp:{$user->id}";
|
|
|
|
|
|
$canGainReward = ! Redis::exists($cooldownKey);
|
|
|
|
|
|
$actualExpGain = 0;
|
|
|
|
|
|
$actualJjbGain = 0;
|
|
|
|
|
|
|
|
|
|
|
|
if ($canGainReward) {
|
|
|
|
|
|
// 经验奖励(支持固定值 "1" 或范围 "1-10")
|
|
|
|
|
|
$expGain = $this->parseRewardValue(Sysparam::getValue('exp_per_heartbeat', '1'));
|
2026-02-26 23:49:55 +08:00
|
|
|
|
$expMultiplier = $this->vipService->getExpMultiplier($user);
|
2026-02-27 00:44:45 +08:00
|
|
|
|
$actualExpGain = (int) round($expGain * $expMultiplier);
|
|
|
|
|
|
$user->exp_num += $actualExpGain;
|
|
|
|
|
|
|
|
|
|
|
|
// 金币奖励(支持固定值 "1" 或范围 "1-5")
|
|
|
|
|
|
$jjbGain = $this->parseRewardValue(Sysparam::getValue('jjb_per_heartbeat', '0'));
|
|
|
|
|
|
if ($jjbGain > 0) {
|
|
|
|
|
|
$jjbMultiplier = $this->vipService->getJjbMultiplier($user);
|
|
|
|
|
|
$actualJjbGain = (int) round($jjbGain * $jjbMultiplier);
|
|
|
|
|
|
$user->jjb = ($user->jjb ?? 0) + $actualJjbGain;
|
|
|
|
|
|
}
|
2026-02-26 23:49:55 +08:00
|
|
|
|
|
2026-02-27 00:44:45 +08:00
|
|
|
|
// 设置冷却(30秒内不再给奖励)
|
|
|
|
|
|
Redis::setex($cooldownKey, 30, 1);
|
2026-02-26 23:49:55 +08:00
|
|
|
|
}
|
2026-02-26 13:35:38 +08:00
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
// 2. 使用 sysparam 表中可配置的等级-经验阈值计算等级
|
|
|
|
|
|
// 管理员(superlevel 及以上)不参与自动升降级,等级由后台手动设置
|
|
|
|
|
|
$superLevel = (int) Sysparam::getValue('superlevel', '100');
|
|
|
|
|
|
$oldLevel = $user->user_level;
|
2026-02-26 13:35:38 +08:00
|
|
|
|
$leveledUp = false;
|
|
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
if ($oldLevel < $superLevel) {
|
|
|
|
|
|
$newLevel = Sysparam::calculateLevel($user->exp_num);
|
|
|
|
|
|
if ($newLevel !== $oldLevel && $newLevel < $superLevel) {
|
|
|
|
|
|
$user->user_level = $newLevel;
|
|
|
|
|
|
$leveledUp = ($newLevel > $oldLevel);
|
|
|
|
|
|
}
|
2026-02-26 13:35:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$user->save(); // 存点入库
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 将新的等级反馈给当前用户的在线名单上
|
|
|
|
|
|
// 确保刚刚升级后别人查看到的也是最准确等级
|
|
|
|
|
|
$this->chatState->userJoin($id, $user->username, [
|
|
|
|
|
|
'level' => $user->user_level,
|
|
|
|
|
|
'sex' => $user->sex,
|
|
|
|
|
|
'headface' => $user->headface,
|
2026-02-26 21:30:07 +08:00
|
|
|
|
'vip_icon' => $user->vipIcon(),
|
|
|
|
|
|
'vip_name' => $user->vipName(),
|
|
|
|
|
|
'vip_color' => $user->isVip() ? ($user->vipLevel?->color ?? '') : '',
|
|
|
|
|
|
'is_admin' => $user->user_level >= $superLevel,
|
2026-02-26 13:35:38 +08:00
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 如果突破境界,向全房系统喊话广播!
|
|
|
|
|
|
if ($leveledUp) {
|
|
|
|
|
|
// 生成炫酷广播消息发向该频道
|
|
|
|
|
|
$sysMsg = [
|
|
|
|
|
|
'id' => $this->chatState->nextMessageId($id),
|
|
|
|
|
|
'room_id' => $id,
|
|
|
|
|
|
'from_user' => '系统传音',
|
|
|
|
|
|
'to_user' => '大家',
|
|
|
|
|
|
'content' => "🌟 天道酬勤!恭喜侠客【{$user->username}】挂机苦修,境界突破至 LV.{$user->user_level}!",
|
|
|
|
|
|
'is_secret' => false,
|
|
|
|
|
|
'font_color' => '#d97706', // 琥珀橙色
|
|
|
|
|
|
'action' => '大声宣告',
|
|
|
|
|
|
'sent_at' => now()->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$this->chatState->pushMessage($id, $sysMsg);
|
|
|
|
|
|
broadcast(new MessageSent($id, $sysMsg));
|
|
|
|
|
|
|
|
|
|
|
|
// 落库
|
|
|
|
|
|
SaveMessageJob::dispatch($sysMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
// 5. 随机事件触发(复刻原版 autoact 系统,概率可在后台配置)
|
|
|
|
|
|
$autoEvent = null;
|
|
|
|
|
|
$eventChance = (int) Sysparam::getValue('auto_event_chance', '10');
|
|
|
|
|
|
if ($eventChance > 0 && rand(1, 100) <= $eventChance) {
|
|
|
|
|
|
$autoEvent = Autoact::randomEvent();
|
|
|
|
|
|
if ($autoEvent) {
|
|
|
|
|
|
// 应用经验/金币变化(不低于 0)
|
|
|
|
|
|
if ($autoEvent->exp_change !== 0) {
|
|
|
|
|
|
$user->exp_num = max(0, $user->exp_num + $autoEvent->exp_change);
|
|
|
|
|
|
}
|
|
|
|
|
|
if ($autoEvent->jjb_change !== 0) {
|
|
|
|
|
|
$user->jjb = max(0, ($user->jjb ?? 0) + $autoEvent->jjb_change);
|
|
|
|
|
|
}
|
|
|
|
|
|
$user->save();
|
|
|
|
|
|
|
|
|
|
|
|
// 重新计算等级(经验可能因事件而变化,但管理员不参与自动升降级)
|
|
|
|
|
|
if ($user->user_level < $superLevel) {
|
|
|
|
|
|
$recalcLevel = Sysparam::calculateLevel($user->exp_num);
|
|
|
|
|
|
if ($recalcLevel !== $user->user_level && $recalcLevel < $superLevel) {
|
|
|
|
|
|
$user->user_level = $recalcLevel;
|
|
|
|
|
|
$user->save();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 广播随机事件消息到聊天室
|
|
|
|
|
|
$eventMsg = [
|
|
|
|
|
|
'id' => $this->chatState->nextMessageId($id),
|
|
|
|
|
|
'room_id' => $id,
|
|
|
|
|
|
'from_user' => '星海小博士',
|
|
|
|
|
|
'to_user' => '大家',
|
|
|
|
|
|
'content' => $autoEvent->renderText($user->username),
|
|
|
|
|
|
'is_secret' => false,
|
|
|
|
|
|
'font_color' => match ($autoEvent->event_type) {
|
|
|
|
|
|
'good' => '#16a34a', // 绿色(好运)
|
|
|
|
|
|
'bad' => '#dc2626', // 红色(坏运)
|
|
|
|
|
|
default => '#7c3aed', // 紫色(中性)
|
|
|
|
|
|
},
|
|
|
|
|
|
'action' => '',
|
|
|
|
|
|
'sent_at' => now()->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$this->chatState->pushMessage($id, $eventMsg);
|
|
|
|
|
|
broadcast(new MessageSent($id, $eventMsg));
|
|
|
|
|
|
SaveMessageJob::dispatch($eventMsg);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 13:35:38 +08:00
|
|
|
|
return response()->json([
|
|
|
|
|
|
'status' => 'success',
|
|
|
|
|
|
'data' => [
|
|
|
|
|
|
'exp_num' => $user->exp_num,
|
2026-02-27 00:44:45 +08:00
|
|
|
|
'jjb' => $user->jjb ?? 0,
|
|
|
|
|
|
'exp_gain' => $actualExpGain,
|
|
|
|
|
|
'jjb_gain' => $actualJjbGain,
|
2026-02-26 13:35:38 +08:00
|
|
|
|
'user_level' => $user->user_level,
|
|
|
|
|
|
'leveled_up' => $leveledUp,
|
2026-02-26 21:10:34 +08:00
|
|
|
|
'is_max_level' => $user->user_level >= $superLevel,
|
|
|
|
|
|
'auto_event' => $autoEvent ? $autoEvent->renderText($user->username) : null,
|
2026-02-26 13:35:38 +08:00
|
|
|
|
],
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 离开房间 (等同于原版 LEAVE.ASP)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $id 房间ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function leave(Request $request, int $id): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
if (! $user) {
|
|
|
|
|
|
return response()->json(['status' => 'error'], 401);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 从 Redis 删除该用户
|
|
|
|
|
|
$this->chatState->userLeave($id, $user->username);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 广播通知他人
|
|
|
|
|
|
broadcast(new UserLeft($id, $user->username))->toOthers();
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json(['status' => 'success']);
|
|
|
|
|
|
}
|
2026-02-26 21:10:34 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取可用头像列表(返回 JSON)
|
|
|
|
|
|
* 扫描 /public/images/headface/ 目录,返回所有可用头像文件名
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function headfaceList(): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$dir = public_path('images/headface');
|
|
|
|
|
|
$files = [];
|
|
|
|
|
|
|
|
|
|
|
|
if (is_dir($dir)) {
|
|
|
|
|
|
$all = scandir($dir);
|
|
|
|
|
|
foreach ($all as $file) {
|
|
|
|
|
|
// 只包含图片文件
|
|
|
|
|
|
if (preg_match('/\.(gif|jpg|jpeg|png|bmp)$/i', $file)) {
|
|
|
|
|
|
$files[] = $file;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 自然排序(1, 2, 3... 10, 11...)
|
|
|
|
|
|
natsort($files);
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json(['headfaces' => array_values($files)]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 修改头像(原版 fw.asp 功能)
|
|
|
|
|
|
* 用户选择一个头像文件名,更新到 usersf 字段
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function changeAvatar(Request $request): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$user = auth()->user();
|
|
|
|
|
|
$headface = $request->input('headface', '');
|
|
|
|
|
|
|
|
|
|
|
|
if (empty($headface)) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => '请选择一个头像'], 422);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件确实存在
|
|
|
|
|
|
if (! file_exists(public_path('images/headface/'.$headface))) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => '头像文件不存在'], 422);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新用户头像
|
|
|
|
|
|
$user->usersf = $headface;
|
|
|
|
|
|
$user->save();
|
|
|
|
|
|
|
2026-02-26 21:49:37 +08:00
|
|
|
|
// 将新头像同步到 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,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 21:10:34 +08:00
|
|
|
|
return response()->json([
|
|
|
|
|
|
'status' => 'success',
|
|
|
|
|
|
'message' => '头像修改成功!',
|
|
|
|
|
|
'headface' => $headface,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 设置房间公告/祝福语(滚动显示在聊天室顶部)
|
|
|
|
|
|
* 需要房间主人或等级达到 level_announcement 配置值
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param int $id 房间ID
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setAnnouncement(Request $request, int $id): JsonResponse
|
|
|
|
|
|
{
|
|
|
|
|
|
$user = Auth::user();
|
|
|
|
|
|
$room = Room::findOrFail($id);
|
|
|
|
|
|
|
|
|
|
|
|
// 权限检查:房间主人 或 等级 >= level_announcement
|
|
|
|
|
|
$requiredLevel = (int) Sysparam::getValue('level_announcement', '10');
|
|
|
|
|
|
if ($user->username !== $room->master && $user->user_level < $requiredLevel) {
|
|
|
|
|
|
return response()->json(['status' => 'error', 'message' => '权限不足,无法修改公告'], 403);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$request->validate([
|
|
|
|
|
|
'announcement' => 'required|string|max:500',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
$room->announcement = $request->input('announcement');
|
|
|
|
|
|
$room->save();
|
|
|
|
|
|
|
|
|
|
|
|
// 广播公告更新到所有在线用户
|
|
|
|
|
|
$sysMsg = [
|
|
|
|
|
|
'id' => $this->chatState->nextMessageId($id),
|
|
|
|
|
|
'room_id' => $id,
|
|
|
|
|
|
'from_user' => '系统公告',
|
|
|
|
|
|
'to_user' => '大家',
|
|
|
|
|
|
'content' => "📢 {$user->username} 更新了房间公告:{$room->announcement}",
|
|
|
|
|
|
'is_secret' => false,
|
|
|
|
|
|
'font_color' => '#cc0000',
|
|
|
|
|
|
'action' => '',
|
|
|
|
|
|
'sent_at' => now()->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$this->chatState->pushMessage($id, $sysMsg);
|
|
|
|
|
|
broadcast(new MessageSent($id, $sysMsg));
|
|
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
|
'status' => 'success',
|
|
|
|
|
|
'message' => '公告已更新!',
|
|
|
|
|
|
'announcement' => $room->announcement,
|
|
|
|
|
|
]);
|
|
|
|
|
|
}
|
2026-02-27 00:44:45 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解析奖励数值配置(支持固定值或范围格式)
|
|
|
|
|
|
*
|
|
|
|
|
|
* 支持格式:
|
|
|
|
|
|
* "5" → 固定返回 5
|
|
|
|
|
|
* "1-10" → 随机返回 1~10 之间的整数
|
|
|
|
|
|
* "0" → 返回 0(关闭该奖励)
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $value 配置值
|
|
|
|
|
|
* @return int 解析后的奖励数值
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function parseRewardValue(string $value): int
|
|
|
|
|
|
{
|
|
|
|
|
|
$value = trim($value);
|
|
|
|
|
|
|
|
|
|
|
|
// 支持范围格式 "min-max"
|
|
|
|
|
|
if (str_contains($value, '-')) {
|
|
|
|
|
|
$parts = explode('-', $value, 2);
|
|
|
|
|
|
$min = max(0, (int) $parts[0]);
|
|
|
|
|
|
$max = max($min, (int) $parts[1]);
|
|
|
|
|
|
|
|
|
|
|
|
return rand($min, $max);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return max(0, (int) $value);
|
|
|
|
|
|
}
|
2026-02-26 13:35:38 +08:00
|
|
|
|
}
|