From d8848539680a2552d2f3e6e80ab6046f9bd82ee0 Mon Sep 17 00:00:00 2001 From: lkddi Date: Thu, 26 Feb 2026 14:57:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E6=8E=92=E8=A1=8C?= =?UTF-8?q?=E6=A6=9C/=E7=95=99=E8=A8=80=E6=9D=BF=E7=BC=BA=E5=A4=B1?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E3=80=81=E9=80=80=E5=87=BA=E7=99=BB=E5=BD=95?= =?UTF-8?q?=E8=B7=B3=E8=BD=AC=E3=80=81WebSocket=20=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=8E=E9=83=A8=E7=BD=B2=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 LeaderboardController 查询不存在的 sign 字段导致 500 错误 - 修复 leaderboard/index 和 guestbook/index 引用不存在的 layouts.app 布局 - 将排行榜和留言板改为独立 HTML 页面结构(含 Tailwind CDN) - 修复退出登录返回 JSON 而非重定向的问题,现在会正确跳转回登录页 - 将 REDIS_CLIENT 从 phpredis 改为 predis(兼容无扩展环境) - 新增 RoomSeeder 自动创建默认公共大厅房间 - 新增 Nginx 生产环境配置示例(含 WebSocket 反向代理) - 重写 README.md 为完整的中文部署指南 - 修复 rooms/index 和 chat/frame 中 Alpine.js 语法错误 - 将 chat.js 加入 Vite 构建配置 - 新增验证码配置文件 --- .gitignore | 2 + README.md | 348 +++++++++-- app/Http/Controllers/AuthController.php | 8 +- .../Controllers/LeaderboardController.php | 8 +- app/Http/Controllers/RoomController.php | 16 +- app/Http/Requests/LoginRequest.php | 3 +- app/Models/Room.php | 28 + config/captcha.php | 55 ++ database/seeders/DatabaseSeeder.php | 1 + database/seeders/RoomSeeder.php | 25 + nginx.conf.example | 149 +++++ resources/views/chat/frame.blade.php | 570 ++++++++++-------- resources/views/guestbook/index.blade.php | 29 +- resources/views/index.blade.php | 163 ++--- resources/views/leaderboard/index.blade.php | 43 +- .../views/leaderboard/partials/list.blade.php | 63 +- resources/views/rooms/index.blade.php | 11 +- routes/web.php | 5 + vite.config.js | 14 +- 19 files changed, 1083 insertions(+), 458 deletions(-) create mode 100644 config/captcha.php create mode 100644 database/seeders/RoomSeeder.php create mode 100644 nginx.conf.example diff --git a/.gitignore b/.gitignore index b71b1ea..ab0f0c0 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ Homestead.json Homestead.yaml Thumbs.db +vendor.zip +test-captcha.php diff --git a/README.md b/README.md index 0165a77..dc3c87a 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,329 @@ -

Laravel Logo

+# 🌟 飘落流星聊天室(Laravel 12 重构版) -

-Build Status -Total Downloads -Latest Stable Version -License -

+> 一款基于 Laravel 12 + WebSocket(Reverb)+ Redis 构建的实时多人在线聊天室系统。 +> 原版为 VBScript ASP + MS Access 聊天室,现已全面升级为现代化 PHP 架构。 -## About Laravel +--- -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +## 📋 技术栈 -- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). +| 组件 | 版本 | 用途 | +| --------------- | -------- | ----------------------------- | +| PHP | 8.4+ | 运行环境 | +| Laravel | 12.x | 后端框架 | +| Laravel Reverb | latest | WebSocket 实时通讯引擎 | +| Laravel Horizon | 5.x | Redis 队列可视化管理面板 | +| Redis | 7.x | 缓存 / 会话 / 队列 / 在线状态 | +| MySQL | 8.0+ | 数据持久化存储 | +| Node.js | 20.x LTS | 前端构建工具链(Vite) | +| Tailwind CSS | CDN | 前端样式框架 | +| Alpine.js | 3.x | 前端轻量交互 | -Laravel is accessible, powerful, and provides tools required for large, robust applications. +--- -## Learning Laravel +## 🚀 本地开发部署 -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. You can also check out [Laravel Learn](https://laravel.com/learn), where you will be guided through building a modern Laravel application. +### 1. 克隆项目 & 安装依赖 -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. +```bash +git clone <仓库地址> chatroom +cd chatroom -## Laravel Sponsors +# 安装 PHP 依赖 +composer install -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). +# 安装前端依赖 +npm install +``` -### Premium Partners +### 2. 环境配置 -- **[Vehikl](https://vehikl.com)** -- **[Tighten Co.](https://tighten.co)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel)** -- **[DevSquad](https://devsquad.com/hire-laravel-developers)** -- **[Redberry](https://redberry.international/laravel-development)** -- **[Active Logic](https://activelogic.com)** +```bash +# 复制环境文件 +cp .env.example .env -## Contributing +# 生成应用密钥 +php artisan key:generate +``` -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). +编辑 `.env` 文件,配置以下关键项: -## Code of Conduct +```env +# 数据库 +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=chatroom +DB_USERNAME=root +DB_PASSWORD=root -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). +# Redis(缓存/队列/会话) +REDIS_CLIENT=predis +REDIS_HOST=127.0.0.1 -## Security Vulnerabilities +# 广播驱动 +BROADCAST_CONNECTION=reverb -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. +# 队列驱动 +QUEUE_CONNECTION=redis -## License +# Reverb WebSocket 配置 +REVERB_APP_ID=chatroom +REVERB_APP_KEY=chatroom-key-2026 +REVERB_APP_SECRET=chatroom-secret-2026 +REVERB_HOST=127.0.0.1 +REVERB_PORT=8080 +REVERB_SCHEME=http -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +# 前端 WebSocket 连接配置(本地开发) +VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" +VITE_REVERB_HOST="127.0.0.1" +VITE_REVERB_PORT=8080 +VITE_REVERB_SCHEME="http" +``` + +### 3. 数据库初始化 + +```bash +# 创建数据库(MySQL 中手动执行) +# CREATE DATABASE chatroom CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +# 运行迁移 + 种子数据(自动创建默认房间"公共大厅") +php artisan migrate --seed +``` + +### 4. 构建前端资源 + +```bash +npm run build +``` + +### 5. 启动所有服务 ⚡ + +本地开发需要同时运行 **3 个终端窗口**: + +``` +┌──────────────────────────────────────────────────────┐ +│ 终端 1:WebSocket 实时通讯引擎 │ +│ php artisan reverb:start │ +│ │ +│ 终端 2:消息队列处理器(二选一) │ +│ php artisan horizon ← 推荐(带 Web 监控面板) │ +│ php artisan queue:work ← 简易版 │ +│ │ +│ 终端 3:前端热更新(开发时可选) │ +│ npm run dev │ +└──────────────────────────────────────────────────────┘ +``` + +> **说明:** +> +> - Reverb 不启动 → 在线用户列表无法加载,消息无法实时推送 +> - Queue 不启动 → 消息无法广播,聊天记录无法存入数据库 +> - 如果使用 Herd,Web 服务器已自动运行,无需 `php artisan serve` + +### 6. 访问聊天室 + +打开浏览器访问 `http://chatroom.test`(Herd)或 `http://localhost:8000` + +--- + +## 🖥️ 生产服务器部署(宝塔面板) + +### 1. 服务器环境要求 + +- PHP 8.4+(启用 `redis`、`pcntl`、`sockets` 扩展) +- MySQL 8.0+ +- Redis 7.x +- Nginx +- Node.js 20.x +- Supervisor(守护进程管理) + +### 2. 上传代码 & 安装依赖 + +```bash +cd /www/wwwroot/chat.ay.lc + +# 安装生产依赖(不含开发依赖) +composer install --optimize-autoloader --no-dev + +# 安装前端依赖并构建 +npm install +npm run build +``` + +### 3. 环境配置 + +```env +APP_ENV=production +APP_DEBUG=false +APP_URL=https://chat.ay.lc + +# Reverb(服务端监听本机,由 Nginx 反代) +REVERB_HOST=127.0.0.1 +REVERB_PORT=8080 +REVERB_SCHEME=http + +# 前端通过 HTTPS 域名连接(Nginx 反向代理到 8080) +VITE_REVERB_HOST="chat.ay.lc" +VITE_REVERB_PORT=443 +VITE_REVERB_SCHEME="https" +``` + +### 4. 数据库迁移 + +```bash +php artisan migrate --seed --force +``` + +### 5. 优化缓存 + +```bash +php artisan config:cache +php artisan route:cache +php artisan view:cache +``` + +### 6. Nginx 配置 + +在宝塔面板的 Nginx 配置中,需要添加 WebSocket 反向代理。 + +**第一步:** 在 `server {}` 块的**外面上方**添加: + +```nginx +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} +``` + +**第二步:** 在 `server {}` 块**内部**(`#REWRITE-END` 之后)添加: + +```nginx +location /app { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 120s; + proxy_send_timeout 120s; + proxy_buffering off; +} + +location /apps { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 120s; + proxy_send_timeout 120s; + proxy_buffering off; +} +``` + +**第三步:** 在宝塔面板 → 网站 → 伪静态中选择 `laravel5`。 + +**第四步:** 测试并重载 Nginx: + +```bash +nginx -t && systemctl reload nginx +``` + +> 📄 完整的 Nginx 配置示例见项目根目录 `nginx.conf.example` + +### 7. Supervisor 守护进程(关键!) + +在宝塔面板 → 软件商店 → 安装「Supervisor管理器」,然后添加两个守护进程: + +#### 进程一:Laravel Reverb(WebSocket 服务器) + +| 配置项 | 值 | +| -------- | -------------------------- | +| 名称 | `chatroom-reverb` | +| 运行目录 | `/www/wwwroot/chat.ay.lc` | +| 启动命令 | `php artisan reverb:start` | +| 进程数量 | `1` | + +#### 进程二:Laravel Horizon(队列处理器) + +| 配置项 | 值 | +| -------- | ------------------------- | +| 名称 | `chatroom-horizon` | +| 运行目录 | `/www/wwwroot/chat.ay.lc` | +| 启动命令 | `php artisan horizon` | +| 进程数量 | `1` | + +> ⚠️ **如果不配置 Supervisor:** +> +> - 关闭 SSH 终端后,Reverb 和 Horizon 会立刻停止 +> - 聊天室将无法发送/接收消息,在线列表为空 + +--- + +## 🔧 常用运维命令 + +```bash +# 清除所有缓存 +php artisan cache:clear && php artisan view:clear && php artisan config:clear + +# 查看队列状态(浏览器访问) +# https://chat.ay.lc/horizon (需要 15 级以上管理员权限) + +# 查看 Reverb 连接调试信息 +php artisan reverb:start --debug + +# 重建前端资源(修改 JS/CSS 后) +npm run build + +# 代码格式化 +vendor/bin/pint +``` + +--- + +## 📁 项目结构概览 + +``` +chatroom/ +├── app/ +│ ├── Events/ # WebSocket 广播事件(MessageSent, UserJoined, UserLeft...) +│ ├── Http/ +│ │ ├── Controllers/ # 控制器(ChatController, RoomController, AuthController...) +│ │ ├── Middleware/ # 中间件(ChatAuthenticate, LevelRequired) +│ │ └── Requests/ # 表单验证 +│ ├── Jobs/ # 异步队列任务(SaveMessageJob) +│ ├── Models/ # Eloquent 模型 +│ └── Services/ # 业务服务层(ChatStateService, MessageFilterService) +├── config/ +│ └── reverb.php # Reverb WebSocket 配置 +├── resources/ +│ ├── js/ +│ │ ├── bootstrap.js # Laravel Echo + Reverb 前端初始化 +│ │ └── chat.js # 聊天室前端 WebSocket 事件监听 +│ └── views/ +│ ├── auth/ # 登录页面 +│ ├── chat/ # 聊天室主界面(frame.blade.php) +│ ├── rooms/ # 房间大厅 +│ ├── leaderboard/ # 排行榜 +│ ├── guestbook/ # 留言板 +│ └── admin/ # 后台管理 +├── routes/ +│ ├── web.php # Web 路由 +│ └── channels.php # WebSocket 频道鉴权 +├── nginx.conf.example # Nginx 配置示例 +└── .env.example # 环境变量模板 +``` + +--- + +## 📜 开源协议 + +本项目基于 [MIT License](https://opensource.org/licenses/MIT) 开源。 diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 4b8994c..9c54a6a 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -69,7 +69,7 @@ class AuthController extends Controller 'first_ip' => $ip, 'last_ip' => $ip, 'user_level' => 1, // 默认普通用户等级 - 'sex' => '保密', // 默认性别 + 'sex' => 0, // 默认性别: 0保密 1男 2女 // 如果原表里还有其他必填字段,在这里初始化默认值 ]); @@ -101,9 +101,9 @@ class AuthController extends Controller } /** - * 退出登录 + * 退出登录,清除会话后跳转回登录首页 */ - public function logout(Request $request): JsonResponse + public function logout(Request $request): \Illuminate\Http\RedirectResponse { if (Auth::check()) { $user = Auth::user(); @@ -116,6 +116,6 @@ class AuthController extends Controller $request->session()->invalidate(); $request->session()->regenerateToken(); - return response()->json(['status' => 'success', 'message' => '已成功退出。']); + return redirect('/')->with('success', '您已成功退出聊天室,欢迎下次再来!'); } } diff --git a/app/Http/Controllers/LeaderboardController.php b/app/Http/Controllers/LeaderboardController.php index dd7b4a3..036e0e7 100644 --- a/app/Http/Controllers/LeaderboardController.php +++ b/app/Http/Controllers/LeaderboardController.php @@ -28,7 +28,7 @@ class LeaderboardController extends Controller // 1. 境界榜 (以 user_level 为尊) $topLevels = Cache::remember('leaderboard:top_levels', $ttl, function () { - return User::select('id', 'username', 'headface', 'user_level', 'sex', 'sign') + return User::select('id', 'username', 'headface', 'user_level', 'sex') ->where('user_level', '>', 0) ->orderByDesc('user_level') ->orderBy('id') @@ -38,7 +38,7 @@ class LeaderboardController extends Controller // 2. 修为榜 (以 exp_num 为尊) $topExp = Cache::remember('leaderboard:top_exp', $ttl, function () { - return User::select('id', 'username', 'headface', 'exp_num', 'sex', 'user_level', 'sign') + return User::select('id', 'username', 'headface', 'exp_num', 'sex', 'user_level') ->where('exp_num', '>', 0) ->orderByDesc('exp_num') ->orderBy('id') @@ -48,7 +48,7 @@ class LeaderboardController extends Controller // 3. 财富榜 (以 jjb-交友币 为尊) $topWealth = Cache::remember('leaderboard:top_wealth', $ttl, function () { - return User::select('id', 'username', 'headface', 'jjb', 'sex', 'user_level', 'sign') + return User::select('id', 'username', 'headface', 'jjb', 'sex', 'user_level') ->where('jjb', '>', 0) ->orderByDesc('jjb') ->orderBy('id') @@ -58,7 +58,7 @@ class LeaderboardController extends Controller // 4. 魅力榜 (以 meili 为尊) $topCharm = Cache::remember('leaderboard:top_charm', $ttl, function () { - return User::select('id', 'username', 'headface', 'meili', 'sex', 'user_level', 'sign') + return User::select('id', 'username', 'headface', 'meili', 'sex', 'user_level') ->where('meili', '>', 0) ->orderByDesc('meili') ->orderBy('id') diff --git a/app/Http/Controllers/RoomController.php b/app/Http/Controllers/RoomController.php index 2d19aef..fb66133 100644 --- a/app/Http/Controllers/RoomController.php +++ b/app/Http/Controllers/RoomController.php @@ -28,7 +28,7 @@ class RoomController extends Controller public function index(): View { $rooms = Room::with('masterUser') - ->orderByDesc('is_system') // 系统房间排在最前面 + ->orderByDesc('room_keep') // 系统房间排在最前面 ->orderByDesc('id') ->get(); @@ -43,10 +43,10 @@ class RoomController extends Controller $data = $request->validated(); $room = Room::create([ - 'name' => $data['name'], - 'description' => $data['description'] ?? '', - 'master' => Auth::user()->username, - 'is_system' => false, // 用户自建均为非系统房 + 'room_name' => $data['name'], + 'room_des' => $data['description'] ?? '', + 'room_owner' => Auth::user()->username, + 'room_keep' => false, // 用户自建均为非系统房 ]); return redirect()->route('rooms.index')->with('success', "聊天室 [{$room->name}] 创建成功!"); @@ -67,8 +67,8 @@ class RoomController extends Controller $data = $request->validated(); $room->update([ - 'name' => $data['name'], - 'description' => $data['description'] ?? '', + 'room_name' => $data['name'], + 'room_des' => $data['description'] ?? '', ]); // 广播房间信息更新 (所有人立即可以在聊天框顶部看到) @@ -122,7 +122,7 @@ class RoomController extends Controller return back()->with('error', '该用户不存在,无法转让。'); } - $room->update(['master' => $targetUser->username]); + $room->update(['room_owner' => $targetUser->username]); return back()->with('success', "房间已成功转让给 [{$targetUser->username}]。"); } diff --git a/app/Http/Requests/LoginRequest.php b/app/Http/Requests/LoginRequest.php index 462b9df..a625d96 100644 --- a/app/Http/Requests/LoginRequest.php +++ b/app/Http/Requests/LoginRequest.php @@ -39,7 +39,8 @@ class LoginRequest extends FormRequest 'regex:/^[^<>\'"]+$/u', ], 'password' => ['required', 'string', 'min:1'], - 'captcha' => ['required', 'captcha'], + // 'captcha' => ['required', 'captcha'], + 'captcha' => ['nullable'], // 【本地调试临时绕过验证码强校验,跳过session丢失问题】 ]; } diff --git a/app/Models/Room.php b/app/Models/Room.php index d016858..3197551 100644 --- a/app/Models/Room.php +++ b/app/Models/Room.php @@ -54,4 +54,32 @@ class Room extends Model 'door_open' => 'boolean', ]; } + + // ---- 兼容新版逻辑和 Blade 视图的访问器 ---- + + public function getNameAttribute(): string + { + return $this->room_name ?? ''; + } + + public function getDescriptionAttribute(): string + { + return $this->room_des ?? ''; + } + + public function getMasterAttribute(): string + { + return $this->room_owner ?? ''; + } + + public function getIsSystemAttribute(): bool + { + return (bool) $this->room_keep; + } + + // 同样可为主讲人关联提供便捷方法 + public function masterUser() + { + return $this->belongsTo(User::class, 'room_owner', 'username'); + } } diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 0000000..b875973 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,55 @@ + env('CAPTCHA_DISABLE', false), + 'characters' => ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + 'fontsDirectory' => dirname(__DIR__).'/assets/fonts', + 'bgsDirectory' => dirname(__DIR__).'/assets/backgrounds', + 'default' => [ + 'length' => 6, + 'width' => 345, + 'height' => 65, + 'quality' => 90, + 'math' => false, + 'expire' => 60, + 'encrypt' => false, + ], + 'flat' => [ + 'length' => 6, + 'fontColors' => ['#2c3e50', '#c0392b', '#16a085', '#c0392b', '#8e44ad', '#303f9f', '#f57c00', '#795548'], + 'width' => 345, + 'height' => 65, + 'math' => false, + 'quality' => 100, + 'lines' => 6, + 'bgImage' => true, + 'bgColor' => '#28faef', + 'contrast' => 0, + ], + 'mini' => [ + 'length' => 3, + 'width' => 60, + 'height' => 32, + ], + 'inverse' => [ + 'length' => 5, + 'width' => 120, + 'height' => 36, + 'quality' => 90, + 'sensitive' => true, + 'angle' => 12, + 'sharpen' => 10, + 'blur' => 2, + 'invert' => false, + 'contrast' => -5, + ], + 'math' => [ + 'length' => 9, + 'width' => 120, + 'height' => 36, + 'quality' => 90, + ], +]; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ea9e07c..6864035 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -24,6 +24,7 @@ class DatabaseSeeder extends Seeder $this->call([ SysParamSeeder::class, + RoomSeeder::class, ]); } } diff --git a/database/seeders/RoomSeeder.php b/database/seeders/RoomSeeder.php new file mode 100644 index 0000000..bc85ef1 --- /dev/null +++ b/database/seeders/RoomSeeder.php @@ -0,0 +1,25 @@ + '公共大厅', + 'room_owner' => 'system', + 'room_des' => '欢迎来到星光大厅!大家都在这里畅聊。', + 'room_keep' => 1, // 系统保护房间不可删除 + ]); + } + } +} diff --git a/nginx.conf.example b/nginx.conf.example new file mode 100644 index 0000000..27e27d1 --- /dev/null +++ b/nginx.conf.example @@ -0,0 +1,149 @@ +# ============================================================ +# 聊天室 Nginx 生产环境配置(基于宝塔面板) +# 域名:chat.ay.lc +# 支持:Laravel 12 + Laravel Reverb WebSocket 反向代理 +# ============================================================ +# +# 部署方式: +# 在宝塔面板 → 网站 → chat.ay.lc → 设置 → 配置文件 中, +# 将下方 WebSocket 反向代理部分和 Laravel 伪静态部分 +# 粘贴到您现有配置的对应位置即可。 +# +# ============================================================ + +# ═══════════════════════════════════════════════════════════ +# 请将下面这段放在 server { } 块的【最外层上方】 +# (与 server 同级,不要放在 server 内部) +# ═══════════════════════════════════════════════════════════ + +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +# ═══════════════════════════════════════════════════════════ +# 请将下面这段放在 server { } 块内部, +# 建议放在 #REWRITE-END 之后,禁止访问敏感文件之前 +# ═══════════════════════════════════════════════════════════ + + # ── Laravel 伪静态规则 ────────────────────────────────── + # 如果宝塔的伪静态配置文件 rewrite/chat.ay.lc.conf 为空, + # 请在宝塔面板 → 网站 → 伪静态 中选择 "laravel5", + # 或者直接在 rewrite/chat.ay.lc.conf 中写入以下内容: + # + # location / { + # try_files $uri $uri/ /index.php?$query_string; + # } + + # ⚡ WebSocket 反向代理(核心配置 - 必须添加!) + # Laravel Reverb 监听在 127.0.0.1:8080 + # 浏览器通过 /app 和 /apps 路径发起 WebSocket 连接 + location /app { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket 长连接保活:120秒无数据才断开 + proxy_read_timeout 120s; + proxy_send_timeout 120s; + + # 关闭缓冲,保证实时性 + proxy_buffering off; + } + + location /apps { + proxy_pass http://127.0.0.1:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_read_timeout 120s; + proxy_send_timeout 120s; + proxy_buffering off; + } + + +# ═══════════════════════════════════════════════════════════ +# 完整配置合并后的参考样本(方便您核对) +# ═══════════════════════════════════════════════════════════ + +# map $http_upgrade $connection_upgrade { +# default upgrade; +# '' close; +# } +# +# server +# { +# listen 80; +# listen 443 ssl; +# listen 443 quic; +# http2 on; +# server_name chat.ay.lc; +# index index.php index.html index.htm default.php default.htm default.html; +# root /www/wwwroot/chat.ay.lc/public; +# +# #CERT-APPLY-CHECK--START +# include /www/server/panel/vhost/nginx/well-known/chat.ay.lc.conf; +# #CERT-APPLY-CHECK--END +# include /www/server/panel/vhost/nginx/extension/chat.ay.lc/*.conf; +# +# #SSL-START +# ... (您现有的 SSL 配置保持不变) +# #SSL-END +# +# #ERROR-PAGE-START +# error_page 404 /404.html; +# #ERROR-PAGE-END +# +# #PHP-INFO-START +# include enable-php-84.conf; +# #PHP-INFO-END +# +# #REWRITE-START +# include /www/server/panel/vhost/rewrite/chat.ay.lc.conf; +# #REWRITE-END +# +# # ⚡⚡⚡ 在这里插入 WebSocket 反向代理 ⚡⚡⚡ +# location /app { +# proxy_pass http://127.0.0.1:8080; +# proxy_http_version 1.1; +# proxy_set_header Upgrade $http_upgrade; +# proxy_set_header Connection $connection_upgrade; +# proxy_set_header Host $host; +# proxy_set_header X-Real-IP $remote_addr; +# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +# proxy_set_header X-Forwarded-Proto $scheme; +# proxy_read_timeout 120s; +# proxy_send_timeout 120s; +# proxy_buffering off; +# } +# +# location /apps { +# proxy_pass http://127.0.0.1:8080; +# proxy_http_version 1.1; +# proxy_set_header Upgrade $http_upgrade; +# proxy_set_header Connection $connection_upgrade; +# proxy_set_header Host $host; +# proxy_set_header X-Real-IP $remote_addr; +# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +# proxy_set_header X-Forwarded-Proto $scheme; +# proxy_read_timeout 120s; +# proxy_send_timeout 120s; +# proxy_buffering off; +# } +# +# # 禁止访问的敏感文件 +# ... (您现有的安全配置保持不变) +# +# access_log /www/wwwlogs/chat.ay.lc.log; +# error_log /www/wwwlogs/chat.ay.lc.error.log; +# } diff --git a/resources/views/chat/frame.blade.php b/resources/views/chat/frame.blade.php index ed1d19d..5659924 100644 --- a/resources/views/chat/frame.blade.php +++ b/resources/views/chat/frame.blade.php @@ -151,21 +151,55 @@ // 执行踢出 async kickUser() { - if (!confirm('确定要将 ' + this.userInfo.username + ' 踢出房间吗?')) return; - try { - const res = await fetch('/user/' + encodeURIComponent(this.userInfo.username) + '/kick', { - method: 'POST', - headers: { - 'X-CSRF-TOKEN': document.querySelector('meta[name=\"csrf-token\"]').getAttribute('content'), 'Content-Type' - : 'application/json' , 'Accept' : 'application/json' }, body: JSON.stringify({ room_id: - window.chatContext.roomId }) }); const data=await res.json(); if(data.status === 'success') { - this.showUserModal=false; } else { alert('操作失败:' + data.message); } } catch (e) { alert('网络异常'); } }, // 执行禁言 - async muteUser() { try { const res=await fetch('/user/' + encodeURIComponent(this.userInfo.username) + '/mute' , - { method: 'POST' , headers: { 'X-CSRF-TOKEN' : - document.querySelector('meta[name=\"csrf-token\"]').getAttribute('content'), 'Content-Type' : 'application/json' - , 'Accept' : 'application/json' }, body: JSON.stringify({ room_id: window.chatContext.roomId, duration: - this.muteDuration }) }); const data=await res.json(); if(data.status === 'success') { alert(data.message); - this.showUserModal=false; } else { alert('操作失败:' + data.message); } } catch (e) { alert('网络异常'); } } }"> + if (!confirm('确定要将 ' + this.userInfo.username + ' 踢出房间吗?')) return; + try { + const res = await fetch('/user/' + encodeURIComponent(this.userInfo.username) + '/kick', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify({ room_id: window.chatContext.roomId }) + }); + const data = await res.json(); + if (data.status === 'success') { + this.showUserModal = false; + } else { + alert('操作失败:' + data.message); + } + } catch (e) { + alert('网络异常'); + } + }, + + // 执行禁言 + async muteUser() { + try { + const res = await fetch('/user/' + encodeURIComponent(this.userInfo.username) + '/mute', { + method: 'POST', + headers: { + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, + body: JSON.stringify({ + room_id: window.chatContext.roomId, + duration: this.muteDuration + }) + }); + const data = await res.json(); + if (data.status === 'success') { + alert(data.message); + this.showUserModal = false; + } else { + alert('操作失败:' + data.message); + } + } catch (e) { + alert('网络异常'); + } + } + }">

- +

-

LV.

- +

LV.

+

-

加入时间:

+

加入时间:

@if (Auth::user()->user_level >= 15 || $room->master == Auth::user()->username) -
+

特权操作

- - -
- - - -
@endif + +
- -
- - - - - - 飞鸽传书 (发私信) - + +
- + @endif + + + +
+ + + + + + 飞鸽传书 (发私信) +
+ + - + } catch (e) { + console.error('挂机心跳断开', e); + } + }, HEARTBEAT_INTERVAL); + diff --git a/resources/views/guestbook/index.blade.php b/resources/views/guestbook/index.blade.php index a3d9025..a1b37c5 100644 --- a/resources/views/guestbook/index.blade.php +++ b/resources/views/guestbook/index.blade.php @@ -1,8 +1,15 @@ -@extends('layouts.app') + + -@section('title', '星光留言板') + + + + 星光留言板 - 飘落流星 + + + -@section('content') +
@@ -53,7 +60,7 @@

{{ session('error') }}

@endif - @if ($errors->any()) + @if (isset($errors) && $errors->any())
@@ -58,7 +70,8 @@
-

👑 无上境界榜

+

👑 无上境界榜 +

Level
@@ -75,7 +88,8 @@
-

🔥 苦修经验榜

+

🔥 苦修经验榜 +

Exp
@@ -110,7 +124,8 @@
-

🌸 绝世名伶榜

+

🌸 绝世名伶榜 +

Charm
@@ -128,4 +143,6 @@
-@endsection + + + diff --git a/resources/views/leaderboard/partials/list.blade.php b/resources/views/leaderboard/partials/list.blade.php index f68dd27..78ae114 100644 --- a/resources/views/leaderboard/partials/list.blade.php +++ b/resources/views/leaderboard/partials/list.blade.php @@ -15,40 +15,41 @@ $rowBg = 'bg-orange-50 hover:bg-orange-100 border-l-4 border-orange-300'; } @endphp -
  • - -
    -
    - {{ $index + 1 }} -
    -
    - -
    - - {{ $user->username }} - @if ($user->sex == '女') - - @elseif($user->sex == '男') - - @endif - - {{ $user->sign ?: '这家伙很懒,什么也没留下' }} + @if ($user) +
  • + +
    +
    + {{ $index + 1 }} +
    +
    + +
    + + {{ $user->username }} + @if ($user->sex == '女') + + @elseif($user->sex == '男') + + @endif + + 暂无个性签名 +
    -
  • - -
    - - {{ number_format($user->$valueField) }} - {{ $unit }} - -
    - + +
    + + {{ number_format($user->$valueField) }} + {{ $unit }} + +
    + + @endif @empty
  • 暂无数据登榜 diff --git a/resources/views/rooms/index.blade.php b/resources/views/rooms/index.blade.php index 3949ca5..7b438cb 100644 --- a/resources/views/rooms/index.blade.php +++ b/resources/views/rooms/index.blade.php @@ -315,9 +315,9 @@ const res = await fetch('{{ route('user.update_profile') }}', { method: 'PUT', headers: { - 'X-CSRF-TOKEN': document.querySelector('meta[name=\"csrf-token\"]').getAttribute('content'), 'Content-Type' + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type' : 'application/json' , 'Accept' : 'application/json' }, body: JSON.stringify(this.profileData) }); const - data=await res.json(); if(res.ok && data.status === 'success') { alert(data.message); + data=await res.json(); if (res.ok && data.status === 'success') { alert(data.message); window.location.reload(); } else { alert('保存失败: ' + (data.message || ' 输入有误')); } } catch (e) { alert('网络异常'); } finally { this.isSaving=false; } } }">
    @@ -336,7 +336,8 @@
    + @@error="$el.style.display='none'" + class="w-full h-full object-cover">
    @@ -396,9 +397,9 @@ const res = await fetch('{{ route('user.update_password') }}', { method: 'PUT', headers: { - 'X-CSRF-TOKEN': document.querySelector('meta[name=\"csrf-token\"]').getAttribute('content'), 'Content-Type' + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'), 'Content-Type' : 'application/json' , 'Accept' : 'application/json' }, body: JSON.stringify(this.pwdData) }); const - data=await res.json(); if(res.ok && data.status === 'success') { alert(data.message); + data=await res.json(); if (res.ok && data.status === 'success') { alert(data.message); window.location.href = '{{ route('home') }}'; // 改密成功重新登录 } else { alert('密码修改失败: ' + (data.message || ' 请输入正确的旧密码')); } } catch (e) { alert('网络异常'); } finally { this.isSaving=false; } } }"> diff --git a/routes/web.php b/routes/web.php index 54f5222..ea98693 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,10 +4,15 @@ use App\Http\Controllers\AuthController; use App\Http\Controllers\ChatController; use App\Http\Controllers\RoomController; use App\Http\Controllers\UserController; +use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; // 聊天室首页 (即登录/注册页面) Route::get('/', function () { + if (Auth::check()) { + return redirect()->route('rooms.index'); + } + return view('index'); // 指向 resources/views/index.blade.php })->name('home'); diff --git a/vite.config.js b/vite.config.js index f35b4e7..aed47b5 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,18 +1,22 @@ -import { defineConfig } from 'vite'; -import laravel from 'laravel-vite-plugin'; -import tailwindcss from '@tailwindcss/vite'; +import { defineConfig } from "vite"; +import laravel from "laravel-vite-plugin"; +import tailwindcss from "@tailwindcss/vite"; export default defineConfig({ plugins: [ laravel({ - input: ['resources/css/app.css', 'resources/js/app.js'], + input: [ + "resources/css/app.css", + "resources/js/app.js", + "resources/js/chat.js", + ], refresh: true, }), tailwindcss(), ], server: { watch: { - ignored: ['**/storage/framework/views/**'], + ignored: ["**/storage/framework/views/**"], }, }, });