- (Phase 8) 后台各维度管理与配置 - (Phase 9) 全自动静默挂机修仙升级 - (Phase 9) 四大维度风云排行榜页面 - (Phase 10) 全站留言板与悄悄话私信功能 - 运行 Pint 代码格式化
201 lines
6.3 KiB
PHP
201 lines
6.3 KiB
PHP
<?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;
|
||
use App\Models\Room;
|
||
use App\Services\ChatStateService;
|
||
use App\Services\MessageFilterService;
|
||
use Illuminate\Http\JsonResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\View\View;
|
||
|
||
class ChatController extends Controller
|
||
{
|
||
public function __construct(
|
||
private readonly ChatStateService $chatState,
|
||
private readonly MessageFilterService $filter,
|
||
) {}
|
||
|
||
/**
|
||
* 进入房间初始化 (等同于原版 INIT.ASP)
|
||
*
|
||
* @param int $id 房间ID
|
||
* @return View|JsonResponse
|
||
*/
|
||
public function init(int $id)
|
||
{
|
||
$room = Room::findOrFail($id);
|
||
$user = Auth::user();
|
||
|
||
// 1. 将当前用户加入到 Redis 房间在线列表
|
||
$this->chatState->userJoin($id, $user->username, [
|
||
'level' => $user->user_level,
|
||
'sex' => $user->sex,
|
||
'headface' => $user->headface,
|
||
]);
|
||
|
||
// 2. 广播 UserJoined 事件,通知房间内的其他人
|
||
broadcast(new UserJoined($id, $user->username, [
|
||
'level' => $user->user_level,
|
||
'sex' => $user->sex,
|
||
'headface' => $user->headface,
|
||
]))->toOthers();
|
||
|
||
// 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();
|
||
|
||
// 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);
|
||
|
||
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);
|
||
}
|
||
|
||
// 1. 每次心跳 +1 点经验
|
||
$user->exp_num += 1;
|
||
|
||
// 2. 检查等级计算:设定简单粗暴的平滑算式:需要经验=等级*等级*10
|
||
// 例如:0级->0点;1级->10点;2级->40点;3级->90点;10级->1000点
|
||
$currentLevel = $user->user_level;
|
||
$requiredExpForNextLevel = ($currentLevel) * ($currentLevel) * 10;
|
||
|
||
$leveledUp = false;
|
||
|
||
if ($user->exp_num >= $requiredExpForNextLevel) {
|
||
$user->user_level += 1;
|
||
$leveledUp = true;
|
||
}
|
||
|
||
$user->save(); // 存点入库
|
||
|
||
// 3. 将新的等级反馈给当前用户的在线名单上
|
||
// 确保刚刚升级后别人查看到的也是最准确等级
|
||
$this->chatState->userJoin($id, $user->username, [
|
||
'level' => $user->user_level,
|
||
'sex' => $user->sex,
|
||
'headface' => $user->headface,
|
||
]);
|
||
|
||
// 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);
|
||
}
|
||
|
||
return response()->json([
|
||
'status' => 'success',
|
||
'data' => [
|
||
'exp_num' => $user->exp_num,
|
||
'user_level' => $user->user_level,
|
||
'leveled_up' => $leveledUp,
|
||
],
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 离开房间 (等同于原版 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']);
|
||
}
|
||
}
|