feat: 实现挂机修仙、排行榜、大厅重构与全站留言板系统
- (Phase 8) 后台各维度管理与配置 - (Phase 9) 全自动静默挂机修仙升级 - (Phase 9) 四大维度风云排行榜页面 - (Phase 10) 全站留言板与悄悄话私信功能 - 运行 Pint 代码格式化
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天全局状态中心 (替代旧版 ASP中的 Application 全局对象)
|
||||
* 依赖 Redis 内存存取实现在线人员列表、发言记录的高并发读写。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\SysParam;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class ChatStateService
|
||||
{
|
||||
/**
|
||||
* 将用户加入到指定房间的在线列表中。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
* @param string $username 用户名
|
||||
* @param array $info 用户详细信息 (头像、等级等)
|
||||
*/
|
||||
public function userJoin(int $roomId, string $username, array $info): void
|
||||
{
|
||||
$key = "room:{$roomId}:users";
|
||||
Redis::hset($key, $username, json_encode($info, JSON_UNESCAPED_UNICODE));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将用户从指定房间的在线列表中移除。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
* @param string $username 用户名
|
||||
*/
|
||||
public function userLeave(int $roomId, string $username): void
|
||||
{
|
||||
$key = "room:{$roomId}:users";
|
||||
Redis::hdel($key, $username);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定房间的所有在线用户列表。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
*/
|
||||
public function getRoomUsers(int $roomId): array
|
||||
{
|
||||
$key = "room:{$roomId}:users";
|
||||
$users = Redis::hgetall($key);
|
||||
|
||||
$result = [];
|
||||
foreach ($users as $username => $jsonInfo) {
|
||||
$result[$username] = json_decode($jsonInfo, true);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一条新发言推入 Redis 列表,并限制最大保留数量,防止内存泄漏。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
* @param array $message 发言数据包
|
||||
* @param int $maxKeep 最大保留条数
|
||||
*/
|
||||
public function pushMessage(int $roomId, array $message, int $maxKeep = 100): void
|
||||
{
|
||||
$key = "room:{$roomId}:messages";
|
||||
Redis::rpush($key, json_encode($message, JSON_UNESCAPED_UNICODE));
|
||||
|
||||
// 仅保留最新的 $maxKeep 条,旧的自动截断弹弃 (-$maxKeep 到 -1 的区间保留)
|
||||
Redis::ltrim($key, -$maxKeep, -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定房间的新发言记录。
|
||||
* 在高频长轮询或前端断线重连拉取时使用。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
* @param int $lastId 客户端收到的最后一条发言的ID
|
||||
*/
|
||||
public function getNewMessages(int $roomId, int $lastId): array
|
||||
{
|
||||
$key = "room:{$roomId}:messages";
|
||||
$messages = Redis::lrange($key, 0, -1); // 获取当前缓存的全部
|
||||
|
||||
$newMessages = [];
|
||||
foreach ($messages as $msgJson) {
|
||||
$msg = json_decode($msgJson, true);
|
||||
if (isset($msg['id']) && $msg['id'] > $lastId) {
|
||||
$newMessages[] = $msg;
|
||||
}
|
||||
}
|
||||
|
||||
return $newMessages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分布式发号器:为房间内的新消息生成绝对递增的 ID。
|
||||
* 解决了 MySQL 自增在极致并发下依赖读锁的问题。
|
||||
*
|
||||
* @param int $roomId 房间ID
|
||||
*/
|
||||
public function nextMessageId(int $roomId): int
|
||||
{
|
||||
$key = "room:{$roomId}:message_seq";
|
||||
|
||||
return Redis::incr($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取系统参数并设置合理的缓存。
|
||||
* 替代每次都去 MySQL query 的性能损耗。
|
||||
*
|
||||
* @param string $alias 别名
|
||||
*/
|
||||
public function getSysParam(string $alias): ?string
|
||||
{
|
||||
return Cache::remember("sys_param:{$alias}", 60, function () use ($alias) {
|
||||
return SysParam::where('alias', $alias)->value('body');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统参数并刷新 Cache 缓存。
|
||||
*
|
||||
* @param string $alias 别名
|
||||
* @param mixed $value 参数值
|
||||
*/
|
||||
public function setSysParam(string $alias, mixed $value): void
|
||||
{
|
||||
Cache::put("sys_param:{$alias}", $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供一个分布式的 Redis 互斥锁包围。
|
||||
* 防止并发抢占资源(如同时创建重名房间,同时修改某一敏感属性)。
|
||||
*
|
||||
* @param string $key 锁名称
|
||||
* @param callable $callback 获得锁后执行的闭包
|
||||
* @param int $timeout 锁超时时间(秒)
|
||||
* @return mixed
|
||||
*/
|
||||
public function withLock(string $key, callable $callback, int $timeout = 5)
|
||||
{
|
||||
$lockKey = "lock:{$key}";
|
||||
// 尝试获取锁,set nx ex
|
||||
$isLocked = Redis::set($lockKey, 1, 'EX', $timeout, 'NX');
|
||||
|
||||
if (! $isLocked) {
|
||||
// 获取锁失败,业务可自行决定抛异常或重试
|
||||
throw new \Exception("The lock {$key} is currently held by another process.");
|
||||
}
|
||||
|
||||
try {
|
||||
return $callback();
|
||||
} finally {
|
||||
Redis::del($lockKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:聊天内容过滤器 (敏感词与 HTML 净化)
|
||||
* 替代旧版 ASP中的 TrStr() / SHTM() 等各种过滤函数。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
class MessageFilterService
|
||||
{
|
||||
/**
|
||||
* 简单的关键词黑名单,未来可放到数据库或 Redis 动态加载
|
||||
*/
|
||||
private array $badWords = [
|
||||
'外挂', '刷单', '脚本', // 示例黑名单
|
||||
];
|
||||
|
||||
/**
|
||||
* 执行过滤净化,保障入库和显示安全。
|
||||
*
|
||||
* @param string $content 原始用户发送内容
|
||||
* @return string 净化后的内容
|
||||
*/
|
||||
public function filter(string $content): string
|
||||
{
|
||||
if (empty($content)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 1. HTML 标签全量脱除,阻绝任意 XSS/HTML 注入
|
||||
$content = strip_tags($content);
|
||||
|
||||
// 2. 敏感词替换
|
||||
foreach ($this->badWords as $word) {
|
||||
if (mb_strpos($content, $word) !== false) {
|
||||
// 将脏字替换为相同长度的 星号 或 提示
|
||||
$replacement = str_repeat('*', mb_strlen($word));
|
||||
$content = str_replace($word, $replacement, $content);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 将连续的空格去重,只保留一个真正的空格
|
||||
$content = preg_replace('/\s+/', ' ', $content);
|
||||
|
||||
return trim($content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:用户等级与权限辅助获取服务
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UserLevelService
|
||||
{
|
||||
/**
|
||||
* 安全地获取用户的当前权限等级。
|
||||
* 结合 Redis 缓存,防止每次判断权限都产生额外的 MySQL 查询。
|
||||
*
|
||||
* @param string $username 目标查询的用户名
|
||||
* @return int 用户等级,如果没有查到默认为 1
|
||||
*/
|
||||
public function getUserLevel(string $username): int
|
||||
{
|
||||
if (empty($username)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return (int) Cache::remember("user_level:{$username}", 60, function () use ($username) {
|
||||
$level = User::where('username', $username)->value('user_level');
|
||||
|
||||
return $level !== null ? $level : 1;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 当用户等级被管理员修改时,调用此方法清空其对应缓存。
|
||||
*
|
||||
* @param string $username 用户名
|
||||
*/
|
||||
public function forgetUserLevel(string $username): void
|
||||
{
|
||||
Cache::forget("user_level:{$username}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user