Files
chatroom/DEVELOPMENT.md

1005 lines
40 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ChatRoom 开发计划与注意事项
> **技术栈**Laravel 12 · PHP 8.4 · Laravel Reverb (WebSocket) · Redis · MySQL 8.0 · Laravel Horizon
> **参照原项目**`/Users/pllx/Web/chat/hp0709`VBScript ASP 聊天室,仅作功能参考,不做数据迁移)
> **目标域名**`http://chatroom.test`Herd 自动配置)| `https://chat.ay.lc`(生产环境)
---
## 一、环境版本要求
| 组件 | 版本 |
| --------------------- | -------------------------- |
| **PHP** | 8.4.5+ |
| **Laravel Framework** | v12.x |
| **Laravel Reverb** | latestWebSocket 服务器) |
| **Laravel Horizon** | v5Redis 队列可视化管理) |
| **PHPUnit** | v11测试框架 |
| **Node.js** | 20.x LTS |
| **MySQL** | 8.0+ |
| **Redis** | 7.x |
---
## 二、代码规范(强制执行)
### 2.1 Laravel Pint 格式化
```bash
# 提交代码前必须运行,修复格式问题
vendor/bin/pint --dirty
# 检查格式问题(不修复)
vendor/bin/pint --test
# 格式化整个项目
vendor/bin/pint
```
### 2.2 PHP 8.4 类型系统(必须遵守)
```php
// ✅ 正确:构造函数属性提升 (Constructor Property Promotion)
class ChatController extends Controller
{
public function __construct(
private readonly ChatStateService $chatState,
private readonly MessageFilterService $filter,
) {}
}
// ❌ 错误:不允许无参空构造函数
class SomeClass
{
public function __construct() {} // 禁止!
}
// ✅ 正确:显式返回类型 + 参数类型提示
public function send(SendMessageRequest $request): JsonResponse
{
// ...
}
// ✅ 正确:使用 PHP 8.4 新特性
// 联合类型
public function findUser(int|string $id): User|null {}
// readonly 属性
class MessageDto
{
public function __construct(
public readonly string $content,
public readonly string $fromUser,
public readonly int $roomId,
) {}
}
```
### 2.3 Laravel 12 中间件配置(重要)
> [!IMPORTANT]
> Laravel 12 已废弃 `Kernel.php`,中间件在 `bootstrap/app.php` 中配置。
```php
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php', // API 路由
channels: __DIR__.'/../routes/channels.php', // WebSocket 频道
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
// 注册聊天室登录验证中间件
$middleware->alias([
'chat.auth' => \App\Http\Middleware\ChatAuthenticate::class,
'chat.level' => \App\Http\Middleware\LevelRequired::class,
'chat.site_owner' => \App\Http\Middleware\SiteOwnerOnly::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//
})->create();
```
### 2.4 中文注释规范(每个文件必须)
```php
<?php
/**
* 文件功能:[本文件的业务职责描述]
*
* 对应原 ASP 文件:[原文件名.asp]
*
* @package App\[命名空间]
* @author ChatRoom Laravel
* @version 1.0.0
*/
namespace App\Services;
class ChatStateService
{
/**
* 用户进入聊天房间,将其信息写入 Redis。
*
* 替代原 ASP 的 Application("_user_list") 字符串拼接操作。
*
* @param int $roomId 房间 ID
* @param string $username 用户名
* @param array $userInfo 用户信息(等级、头像、性别等)
*/
public function userJoin(int $roomId, string $username, array $userInfo): void
{
// 将用户信息序列化后存入 Redis HashKey 为 "room:{房间ID}:users"
$this->redis->hset("room:{$roomId}:users", $username, json_encode($userInfo));
}
}
```
---
## 三、首次启动(必须先执行)
```bash
cd /Users/pllx/Web/Herd/chatroom
# 安装 PHP 依赖(已完成)
composer install
# 创建数据库(已完成)
mysql -u root -proot -e "CREATE DATABASE IF NOT EXISTS chatroom CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
# 运行数据库迁移 + 基础数据填充
php artisan migrate --seed
# 安装前端依赖并构建(已完成)
npm install
npm run build
```
**开发时运行的服务**
```bash
php artisan reverb:start --debug # WebSocket 服务器 :8080
php artisan horizon # 队列 Worker含 Web 监控 /horizon
npm run dev # Vite 热更新(开发阶段)
# HTTP 由 Herd 自动提供 chatroom.test
```
---
## 四、数据库迁移对照表
**原 Access 表****Laravel Migration** 对应关系:
| 原 ASP 表 | Laravel 迁移文件 | Model 类 | 说明 |
| ----------- | ---------------------------------- | ------------------- | ---------------------------------- |
| `user` | `create_users_table` | `User` | 主用户表(含 jjb 金币 / exp 经验) |
| `room` | `create_rooms_table` | `Room` | 聊天房间 |
| _(内存)_ | `create_messages_table` | `Message` | 消息归档(实时用 Redis |
| `sysparam` | `create_sys_params_table` | `Sysparam` | 系统参数alias/guidetxt/body |
| `ip_lock` | `create_ip_locks_table` | `IpLock` | IP 封锁 |
| `record` | `create_audit_logs_table` | `AuditLog` | 管理操作日志 |
| `guestbook` | `create_guestbooks_table` | `Guestbook` | 留言板 |
| `calls` | `create_friend_calls_table` | `FriendCall` | 好友呼叫 |
| `friendrq` | `create_friend_requests_table` | `FriendRequest` | 好友申请 |
| `action` | `create_actions_table` | `Action` | 表情/动作定义 |
| `admincz` | `create_admin_logs_table` | `AdminLog` | 管理员操作统计 |
| `gg` | `create_user_items_table` | `UserItem` | 道具(封口令等) |
| `scrollad` | `create_scroll_ads_table` | `ScrollAd` | 滚动公告 |
| `hy` / `lh` | `create_marriages_table` | `Marriage` | 婚姻关系 |
| `ip` | `create_ip_logs_table` | `IpLog` | IP 登录日志 |
| `room_des` | `create_room_descriptions_table` | `RoomDescription` | 房间描述模板 |
| `autoact` | `create_autoacts_table` | `Autoact` | 机器人随机事件 |
| _(新增)_ | `create_gifts_table` | `Gift` | 礼物/鲜花定义(名称/图标/金币) |
| _(新增)_ | `create_shop_items_table` | `ShopItem` | 商店商品(特效卡/改名卡等) |
| _(新增)_ | `create_user_purchases_table` | `UserPurchase` | 用户购买记录 |
| _(新增)_ | `create_vip_levels_table` | `VipLevel` | VIP 会员等级(倍率/进入模板) |
| _(新增)_ | `create_ai_provider_configs_table` | `AiProviderConfig` | AI 服务商配置(多厂商支持) |
| _(新增)_ | `create_ai_usage_logs_table` | `AiUsageLog` | AI 使用日志Token 用量记录) |
| _(新增)_ | `create_username_blacklist_table` | `UsernameBlacklist` | 用户名黑名单 |
| _(新增)_ | `create_user_currency_logs_table` | `UserCurrencyLog` | 积分流水(经验/金币/魅力变动记录) |
---
## 五、推荐目录结构
```
app/
├── Events/ # WebSocket 广播事件ShouldBroadcast
│ ├── MessageSent.php # 消息发送(替代 NEWSAY.ASP
│ ├── UserJoined.php # 用户进房(替代 INIT.ASP
│ ├── UserLeft.php # 用户离开(替代 LEAVE.ASP
│ ├── UserKicked.php # 踢人
│ ├── UserMuted.php # 封口
│ ├── UserLevelUp.php # 用户升级通知
│ ├── RoomTitleUpdated.php # 房间标题更新
│ ├── ScreenCleared.php # 管理员清屏
│ └── EffectBroadcast.php # 特效广播(烟花/下雨等)
├── Services/ # 业务逻辑服务层(纯 PHP不含 HTTP 逻辑)
│ ├── ChatStateService.php # Redis 全局状态(替代 Application 对象)
│ ├── MessageFilterService.php # 敏感词/HTML 过滤
│ ├── UserLevelService.php # 等级权限判断(替代 getLevel()
│ ├── AiChatService.php # AI 聊天服务多厂商适配OpenAI/DeepSeek 等)
│ ├── VipService.php # VIP 开通/到期检查
│ ├── ShopService.php # 商店购买 + 道具激活逻辑
│ └── UserCurrencyService.php # ⭐ 积分统一操作服务(所有经验/金币/魅力变更必须经此)
├── Http/
│ ├── Controllers/
│ │ ├── AuthController.php # DEFAULT.asp + CHECK.asp + CLOSE.ASP
│ │ ├── ChatController.php # NEWSAY.ASP + INIT.ASP + LEAVE.ASP + 心跳
│ │ ├── RoomController.php # ROOM*.ASP 系列(大厅/创建/编辑/删除/转让)
│ │ ├── UserController.php # USERSET + DOUSER + KILLUSER + 封禁/解封
│ │ ├── GuestbookController.php # GUEST.ASP留言板
│ │ ├── LeaderboardController.php # TOP10.ASP排行榜
│ │ ├── ShopController.php # 商店购买 + 改名
│ │ ├── ChatBotController.php # AI 聊天机器人接口
│ │ ├── FishingController.php # 钓鱼小游戏(复刻原版 diaoyu/ 功能)
│ │ ├── AdminCommandController.php # 聊天室内管理员快捷命令
│ │ └── Admin/
│ │ ├── DashboardController.php # 后台首页总览
│ │ ├── SystemController.php # VIEWSYS.ASP 系统参数配置
│ │ ├── UserManagerController.php # 用户大盘管理
│ │ ├── RoomManagerController.php # 房间大盘管理
│ │ ├── AutoactController.php # 随机事件管理
│ │ ├── VipController.php # VIP 等级 CRUD
│ │ ├── AiProviderController.php # AI 厂商配置管理(含启用/禁用/设为默认)
│ │ ├── CurrencyStatsController.php # ⭐ 积分流水活动统计(每日来源产出分析)
│ │ └── SmtpController.php # 邮件发信配置
│ │
│ ├── Middleware/
│ │ ├── ChatAuthenticate.php # 聊天室登录验证(检查 Session
│ │ ├── LevelRequired.php # 等级权限中间件chat.level:super
│ │ └── SiteOwnerOnly.php # 超级站长专属chat.site_owner
│ │
│ └── Requests/ # Form Request 验证
│ ├── LoginRequest.php
│ ├── SendMessageRequest.php
│ └── CreateRoomRequest.php
├── Models/
│ ├── User.php ├── Room.php ├── Message.php
│ ├── Sysparam.php ├── IpLock.php ├── IpLog.php
│ ├── AuditLog.php ├── AdminLog.php ├── Guestbook.php
│ ├── FriendCall.php ├── FriendRequest.php ├── Marriage.php
│ ├── Action.php ├── Autoact.php ├── ScrollAd.php
│ ├── RoomDescription.php ├── UserItem.php ├── Gift.php
│ ├── ShopItem.php ├── UserPurchase.php ├── VipLevel.php
│ ├── AiProviderConfig.php ├── AiUsageLog.php ├── UsernameBlacklist.php
│ ├── UserCurrencyLog.php # ⭐ 积分流水记录
│ └── System/
│ └── PlatformSmtpAccount.php
├── Enums/
│ └── CurrencySource.php # ⭐ 积分来源枚举auto_save/fishing_gain/admin_adjust 等)
└── Jobs/
└── SaveMessageJob.php # 异步将消息持久化到 MySQL
bootstrap/
└── app.php # ⚠ Laravel 12中间件/路由在此配置(无 Kernel.php
routes/
├── web.php # 所有 HTTP 路由
├── api.php # API 路由(验证码等)
└── channels.php # WebSocket Presence Channel 鉴权
```
---
## 六、具体开发任务计划
> **图例**`[x]` 已完成 · `[/]` 进行中 · `[ ]` 待开发
---
### ✅ 第一阶段:数据库层
- [x] 修改默认 `users` 迁移username/jjb/exp/sex/headface/user_level/email 等)
- [x] 创建 `rooms` 迁移room_name/owner/auto/des/title/permit_level/door_open
- [x] 创建 `messages` 迁移room_id/from_user/to_user/content/is_secret/font_color/action/sent_at
- [x] 创建 `sys_params` 迁移alias/guidetxt/body
- [x] 创建 `ip_locks` 迁移
- [x] 创建 `audit_logs` 迁移
- [x] 创建 `guestbooks` 迁移
- [x] 创建 `friend_calls` 迁移
- [x] 创建 `friend_requests` 迁移
- [x] 创建 `actions` 迁移
- [x] 创建 `admin_logs` 迁移
- [x] 创建 `user_items` 迁移(封口令道具)
- [x] 创建 `scroll_ads` 迁移
- [x] 创建 `marriages` 迁移
- [x] 创建 `ip_logs` 迁移
- [x] 创建 `room_descriptions` 迁移
- [x] 创建 `autoacts` 迁移(随机事件/机器人自动发言)
- [x] 创建 `gifts` 迁移礼物定义emoji/cost/charm
- [x] 创建 `shop_items` 迁移(商店商品:特效卡/改名卡)
- [x] 创建 `user_purchases` 迁移(购买记录)
- [x] 创建 `vip_levels` 迁移VIP 等级:倍率/进入模板/价格)
- [x] 创建 `ai_provider_configs` 迁移AI 服务商配置)
- [x] 创建 `ai_usage_logs` 迁移AI 用量日志)
- [x] 创建 `username_blacklist` 迁移(用户名黑名单)
- [x] 创建所有 Model 文件(含 `$fillable``$casts`、关联关系、中文 DocBlock
- [x] 创建 `DatabaseSeeder`(默认房间「公共大厅」、系统参数)
- [x] 运行 `php artisan migrate --seed` 验证建表成功
---
### ✅ 第二阶段Auth 认证
- [x] `AuthController::login()` — 登录(含 IP 封锁检查 + 密码 bcrypt
- [x] `AuthController::logout()` — 退出并清理 Redis 用户状态
- [x] `ChatAuthenticate` 中间件 — 检查 Session 是否有效
- [x] `LevelRequired` 中间件 — 权限等级检查(`chat.level:super`
- [x] `SiteOwnerOnly` 中间件 — 超级站长专属(最高权限区块)
- [x] 登录 Blade 视图 `resources/views/index.blade.php`
- [x] 测试:登录 → 退出完整流程
---
### ✅ 第三阶段Redis 状态层
- [x] `ChatStateService` — 完整实现userJoin/userLeave/getRoomUsers/pushMessage/nextMsgId/withLock/getSysParam
- [x] `MessageFilterService` — 敏感词替换 + HTML 过滤
- [x] `UserLevelService` — 等级权限判断
---
### ✅ 第四阶段WebSocket 广播
- [x] `MessageSent` Event广播到 `room.{id}` Presence Channel
- [x] `UserJoined` Event — 用户进入广播
- [x] `UserLeft` Event — 用户离开广播
- [x] `UserKicked` Event — 踢人广播(私有频道)
- [x] `UserMuted` Event — 封口广播
- [x] `UserLevelUp` Event — 升级通知广播
- [x] `RoomTitleUpdated` Event — 标题更新广播
- [x] `ScreenCleared` Event — 管理员清屏广播
- [x] `EffectBroadcast` Event — 特效广播(烟花/下雨/闪电/彩带)
- [x] `routes/channels.php` — Presence Channel 鉴权
- [x] `resources/js/chat.js` — Laravel Echo 接入
- [x] Reverb 连通性测试通过
---
### ✅ 第五阶段:聊天核心
- [x] `ChatController::init()` — 进入房间(历史消息 + 在线用户)
- [x] `ChatController::send()` — 发言(过滤 → Redis → SaveMessageJob → 广播)
- [x] `ChatController::heartbeat()` — 挂机心跳限流每分钟6次
- [x] `ChatController::leave()` — 离开房间
- [x] `ChatController::headfaceList()` — 头像列表
- [x] `ChatController::changeAvatar()` — 修改头像
- [x] `ChatController::setAnnouncement()` — 设置房间公告
- [x] `ChatController::sendFlower()` — 送花/礼物(扣金币 + 增魅力)
- [x] `SaveMessageJob` — 异步消息持久化到 MySQL
- [x] 聊天主界面 Blade 视图 `resources/views/chat/frame.blade.php`
- [x] 测试:进房 → 发言 → 实时收到消息
---
### ✅ 第六阶段:房间管理
- [x] `RoomController::index()` — 大厅房间列表
- [x] `RoomController::store()` — 创建房间
- [x] `RoomController::update()` — 修改设置
- [x] `RoomController::destroy()` — 删除/解散房间
- [x] `RoomController::transfer()` — 转让房主
- [x] 对应 Blade 视图(大厅列表、引导页)
---
### ✅ 第七阶段:用户管理
- [x] `UserController::show()` — 用户名片/资料页
- [x] `UserController::updateProfile()` — 修改个人资料
- [x] `UserController::changePassword()` — 改密码
- [x] `UserController::kick()` — 踢人
- [x] `UserController::mute()` — 封口
- [x] `UserController::ban()` — 封号
- [x] `UserController::banIp()` — 封 IP
- [x] 邮箱验证码发送(`Api\VerificationController`
---
### ✅ 第八阶段:管理后台
- [x] `Admin\DashboardController` — 后台首页总览
- [x] `Admin\SystemController` — 系统参数配置
- [x] `Admin\SmtpController` — 邮件发信配置
- [x] `Admin\UserManagerController` — 用户大盘管理(编辑/删除)
- [x] `Admin\RoomManagerController` — 房间大盘管理
- [x] `Admin\AutoactController` — 随机事件 CRUD 管理
- [x] `Admin\VipController` — VIP 等级 CRUD 管理
- [x] `Admin\AiProviderController` — AI 厂商配置管理(含启用/禁用/设为默认)
- [x] Horizon 面板 `/horizon`(队列监控)
- [x] 对应 Blade 视图
---
### ✅ 第九阶段:外围功能
- [x] `LeaderboardController` — 风云排行榜(经验/金币/魅力榜)
- [x] `GuestbookController` — 留言板(发表/删除)
---
### ✅ 第十阶段:扩展玩法
- [x] **钓鱼小游戏** `FishingController` — 复刻原版 `diaoyu/` 功能(投竿/收竿/随机收益)
- [x] **AI 聊天机器人** `ChatBotController` + `AiChatService`多厂商OpenAI/DeepSeek/Gemini 等)
- [x] **礼物/送花系统**`Gift` 模型 + `ChatController::sendFlower()`(金币消耗 + 魅力增量)
- [x] **商店系统** `ShopController` + `ShopService`(购买特效卡/改名卡,`ShopItem` + `UserPurchase`
- [x] **VIP 会员系统** `VipService` + `VipLevel`(进入/离开专属模板、倍率加成)
- [x] **管理员快捷命令** `AdminCommandController`(警告/踢人/封口/冻结/清屏/全服广播/特效)
- [x] **特效系统** `EffectBroadcast` Event烟花/下雨/闪电/彩带,支持单次卡/周卡)
---
### ✅ 第十一阶段:积分流水审计系统
- [x] `App\Enums\CurrencySource` — 所有来源枚举(可扩展)
- [x] `App\Models\UserCurrencyLog` — 流水 Model含查询 Scope
- [x] `App\Services\UserCurrencyService` — 统一积分变更服务(**所有经验/金币/魅力修改必须走此服务**
- [x] 接入 `AutoSaveExp`auto_save 来源)
- [x] 接入 `FishingController`fishing_cost / fishing_gain 来源)
- [x] 接入 `ChatController::init()` 新人礼包newbie_bonus 来源)
- [x] 接入 `Admin\UserManagerController`admin_adjust 来源)
- [x] `LeaderboardController::todayIndex()` — 今日风云榜独立页(`/leaderboard/today`
- [x] `LeaderboardController::myLogs()` — 用户个人流水日志(`/my/currency-logs`
- [x] `Admin\CurrencyStatsController` — 后台积分活动统计(`/admin/currency-stats`
- [x] 导航栏新增「📅 今日榜」按钮,后台侧边栏新增「📈 积分流水统计」入口
---
### 🔲 待完善事项
- [x] **用户名禁词管理**(已完成)
- `username_blacklist` 表新增 `type``temp`/`permanent`)和 `reason` 字段
- `temp` = 改名后旧名30天保留`permanent` = 管理员永久禁用词
- `UsernameBlacklist::isBlocked()` 精确匹配两种类型,`isReserved()` 保留兼容
- 注册(`AuthController`)和改名(`ShopService::useRenameCard`)均经过检测
- 后台「🚫 禁用用户名」页面(站长专用):
- 单个添加 / 批量添加(多行/逗号/空格分隔,自动去重)
- 关键词搜索 + 分页列表
- 行内编辑原因备注、删除
- 已预置:系统保留词、国家领导人名称、侮辱性词汇
- [ ] **滚动公告管理**(后台 CRUD`ScrollAd` 模型已建,界面待完成)
- [x] **好友系统**(已完成)
- 好友面板独立 partial`resources/views/chat/partials/friend-panel.blade.php`
- 工具栏「好友」按钮,点击弹出浮窗
- 「我关注的好友」列表:显示互相状态(💚互相/👤单向)、添加时间、删除按钮
- 「对方已加我」列表:显示对方添加时间、➕ 回加按钮
- 搜索栏:输入用户名 Enter/点击直接添加好友
- 所有操作调用与双击用户卡片相同的后端接口
- `FriendController::index()` 返回 `friends`(含 `sub_time`)和 `pending` 两个列表
- [ ] **婚姻系统**`Marriage` 模型已建,界面待完成)
- [ ] **单元测试**(核心 Service 层测试覆盖率)
- [ ] **Flash 游戏替代**`game/``pig/` 等 .swf 文件,考虑 HTML5/Canvas 重写)
---
## 七、注意事项
### 7.1 密码策略
- 所有用户密码使用 `bcrypt``Hash::make()`
- 登录时使用 `Hash::check()` 验证
### 7.2 实时推送机制
**聊天室所有状态变更均通过 Reverb WebSocket 广播**,无需轮询刷新。
> [!NOTE]
> 历史上基于 `<meta http-equiv=refresh>` 的 6秒刷新方案已被完全取代任何涉及聊天室状态更新的功能都应通过 Event → Broadcasting 实现,**禁止在实现中引入轮询逻辑**。
### 7.3 Redis 状态层设计
| 数据 | Redis Key 格式 | 说明 |
| ------------ | ------------------------- | -------------------------- |
| 在线用户列表 | `room:{id}:users` Hash | 值为用户信息 JSON |
| 消息环形缓冲 | `room:{id}:messages` List | 最多保留 200 条 |
| 房间基本信息 | `room:{id}:info` String | JSON 快照 |
| 并发锁 | `Cache::lock("key", 10)` | 使用 `->block(5, fn)` 实现 |
### 7.4 Flash 游戏(暂不处理)
`game/``pig/``Gupiao/` 等目录内的 `.swf` Flash 文件现代浏览器已不支持,**本期不做转换**。
### 7.5 AI 机器人多厂商支持
`AiChatService` 支持以下 Provider通过 `ai_provider_configs` 后台配置切换:
| Provider | 推荐模型 | 说明 |
| ---------- | ------------------ | ---------------------------- |
| `openai` | `gpt-4o-mini` | OpenAI 官方 API |
| `deepseek` | `deepseek-chat` | 国产低成本,兼容 OpenAI 接口 |
| `gemini` | `gemini-1.5-flash` | Google Gemini |
### 7.6 商店商品类型说明
| type | slug 示例 | 说明 |
| ---------- | ---------------- | ---------------------- |
| `instant` | `once_fireworks` | 单次特效卡(即时生效) |
| `duration` | `week_fireworks` | 周卡7天内持续生效 |
| `rename` | `rename` | 改名卡(改变显示名称) |
---
### 7.7 积分流水系统 — 开发者使用指南 ⭐
> [!IMPORTANT]
> **铁律**:所有涉及修改用户 `exp_num`(经验)、`jjb`(金币)、`meili`(魅力)的操作,
> **必须通过 `UserCurrencyService::change()` 执行**,禁止直接操作 `$user->increment()` 或 `User::update()`。
#### 8.1 注入服务
```php
use App\Enums\CurrencySource;
use App\Services\UserCurrencyService;
public function __construct(
private readonly UserCurrencyService $currencyService,
) {}
```
#### 8.2 单次变更
```php
// 给用户增加经验(正数=增加,负数=扣除)
$this->currencyService->change(
user: $user,
currency: 'exp', // 'exp' | 'gold' | 'charm'
amount: 10, // 正数=增加,负数=消耗不会低于0
source: CurrencySource::AUTO_SAVE, // 来源(必须用 Enum不得写裸字符串
remark: '挂机获得经验', // 可选备注
roomId: 1, // 可选房间ID
);
// 扣金币(钓鱼消耗)
$this->currencyService->change($user, 'gold', -5, CurrencySource::FISHING_COST, '抛竿消耗');
```
#### 8.3 批量变更(自动存点场景)
```php
// 对多个用户批量发放,内部逐条原子执行
$this->currencyService->batchChange(
users: $onlineUsers, // Collection<User>
currency: 'exp',
amount: 1,
source: CurrencySource::AUTO_SAVE,
remark: '定时存点',
);
```
#### 8.4 添加新的积分来源(扩展 Enum
`App\Enums\CurrencySource` 枚举中新增一个常量即可,**无需修改数据库**
```php
// app/Enums/CurrencySource.php
enum CurrencySource: string
{
case AUTO_SAVE = 'auto_save';
case FISHING_COST = 'fishing_cost';
case FISHING_GAIN = 'fishing_gain';
case NEWBIE_BONUS = 'newbie_bonus';
case ADMIN_ADJUST = 'admin_adjust';
// ↓ 新增活动时在此追加一行即可
case DAILY_SIGN_IN = 'daily_sign_in'; // 示例:每日签到
/** 返回可读名称(用于后台统计展示) */
public function label(): string
{
return match($this) {
self::AUTO_SAVE => '⏰ 自动存点',
self::FISHING_COST => '🎣 钓鱼消耗',
self::FISHING_GAIN => '🐟 钓鱼收获',
self::NEWBIE_BONUS => '🎁 新人礼包',
self::ADMIN_ADJUST => '🛠️ 管理员调整',
self::DAILY_SIGN_IN => '📅 每日签到', // 对应新增
};
}
}
```
#### 8.5 流水查询 Scope
```php
use App\Models\UserCurrencyLog;
// 查询某用户最近7天的经验记录
$logs = UserCurrencyLog::where('user_id', $user->id)
->currency('exp')
->whereDate('created_at', '>=', now()->subDays(7))
->orderByDesc('created_at')
->get();
// 今日金币排行今日获得量前20名
$ranking = UserCurrencyLog::whereDate('created_at', today())
->where('currency', 'gold')
->where('amount', '>', 0) // 只算增加,不算消耗
->selectRaw('user_id, username, SUM(amount) as total')
->groupBy('user_id', 'username')
->orderByDesc('total')
->limit(20)
->get();
```
#### 8.6 相关页面路由速查
| 页面 | 路由名 | URI |
| ------------ | ---------------------------- | --------------------------------------- |
| 个人积分日志 | `currency.my-logs` | `/my/currency-logs?currency=exp&days=7` |
| 今日风云榜 | `leaderboard.today` | `/leaderboard/today` |
| 累计风云榜 | `leaderboard.index` | `/leaderboard` |
| 后台积分统计 | `admin.currency-stats.index` | `/admin/currency-stats?date=2026-02-28` |
---
### 7.8 全局弹窗系统 `window.chatDialog` ⭐
> [!IMPORTANT]
> 聊天室内**禁止使用**浏览器原生的 `alert()` / `confirm()` / `prompt()`。
> 原因:原生弹窗在 Chrome 中会与 `beforeunload` / `pagehide` 事件产生冲突,导致弹窗闪烁消失。
> **所有需要提示或确认的场景,必须使用 `window.chatDialog`。**
#### 文件位置
```
resources/views/chat/partials/global-dialog.blade.php ← 弹窗 HTML + JS
resources/views/chat/frame.blade.php ← 已 @include全页面可用
```
#### API 说明
| 方法 | 模式 | 返回值 | 说明 |
| ----------------------------------------- | ----------------- | ------------------ | ---------------- |
| `chatDialog.alert(msg, title?, color?)` | 仅「确定」按钮 | `Promise<void>` | 替代 `alert()` |
| `chatDialog.confirm(msg, title?, color?)` | 「取消」+「确定」 | `Promise<boolean>` | 替代 `confirm()` |
参数说明:
- `msg`:弹窗正文内容(字符串)
- `title`:标题栏文字,默认 `'提示'` / `'请确认'`
- `color`标题栏背景色CSS 颜色值),默认蓝色 `#336699` / 红色 `#cc4444`
#### 使用示例
```javascript
// ① alert 模式(提示成功 — 绿色)
await window.chatDialog.alert("操作成功!", "提示", "#16a34a");
// ② alert 模式(提示失败 — 红色)
await window.chatDialog.alert("网络异常,请稍后重试", "错误", "#cc4444");
// ③ confirm 模式(执行危险操作前确认)
const yes = await window.chatDialog.confirm(
"确定要撤销该用户的职务吗?撤销后将不再拥有相关权限。",
"撤销职务", // 标题默认红色
);
if (yes) {
// 用户点了「确定」
}
// ④ confirm 模式 — 自定义颜色
const leave = await window.chatDialog.confirm(
"确定要离开聊天室吗?",
"离开聊天室",
"#cc4444",
);
if (leave) {
leaveRoom();
}
```
#### 在 Alpine.js 组件内使用
`userCardComponent` 组件已通过代理方法封装:
```javascript
// 在 Alpine.js 组件方法中,可以直接用 this.$alert / this.$confirm
await this.$alert("任命成功!", "任命成功 ✨", "#16a34a");
const ok = await this.$confirm("确定撤销职务?", "撤销确认");
if (ok) {
/* ... */
}
```
这两个方法内部等价于 `window.chatDialog.alert()` / `window.chatDialog.confirm()`
#### 颜色速查
| 场景 | 推荐颜色 |
| --------------- | ----------------------- |
| 成功 / 正向操作 | `#16a34a`(绿色) |
| 错误 / 警告 | `#cc4444`(红色) |
| 一般提示 | `#336699`(蓝色,默认) |
| 撤销 / 中性操作 | `#6b7280`(灰色) |
| 管理 / 特权操作 | `#7c3aed`(紫色) |
---
### 7.9 全局右下角 Toast 通知卡片 `window.chatToast` ⭐
> [!NOTE]
> `chatToast` 是固定在**右下角**的轻量通知卡片,适用于实时事件通知(奖励到账、好友动态等)。
> 与 `chatDialog` 不同,它不阻断操作流程,自动消失,可堆叠多条。
#### 文件位置
```
resources/views/chat/partials/toast-notification.blade.php ← Toast 组件 HTML + JS
resources/views/chat/frame.blade.php ← 已 @include全页面可用
```
#### API 说明
```javascript
window.chatToast.show({
title: "标题文字", // 必填
message: "正文内容", // 必填,支持 HTML
icon: "🪙", // 可选,左侧 Emoji默认 💬
color: "#f59e0b", // 可选,强调色,默认 #336699
duration: 6000, // 可选自动消失毫秒0 = 不自动消失,默认 6000
action: {
// 可选,操作按钮
label: "操作文字",
onClick: () => {
/* ... */
},
},
});
```
#### 通过消息字段自动触发(后端控制)
后端 broadcast 的消息中包含 `toast_notification` 字段,且接收方是当前用户时,前端脚本会自动弹出 Toast
```php
// AdminCommandController::reward() 示例
$msg['toast_notification'] = [
'title' => '🪙 奖励金币到账',
'message' => "<b>{$admin->username}</b> 向你发放了 <b>{$amount}</b> 枚金币!",
'icon' => '🪙',
'color' => '#f59e0b',
'duration' => 8000,
];
```
#### 使用场景
| 场景 | 颜色 | 图标 |
| ---------------- | --------------- | ---- |
| 奖励金币到账 | `#f59e0b`(橙) | 🪙 |
| 好友动态通知 | `#6b7280`(灰) | 👥 |
| 礼物收到 | `#e11d48`(玫) | 🎁 |
| 系统提示(普通) | `#336699`(蓝) | 💬 |
| 等级晋升 | `#7c3aed`(紫) | 🌟 |
#### 原 `showFriendToast` 迁移说明
旧函数 `showFriendToast()` 已被 `window.chatToast.show()` 替代,好友删除通知已改用新 API。
新增功能只需调用 `window.chatToast.show()`**勿新增** `showFriendToast` 调用。
---
### 7.10 全局大卡片通知 `window.chatBanner` ⭐
> [!NOTE]
> `chatBanner` 是居中弹出的沉浸式大卡片通知组件,适用于好友通知、任命公告、系统广播等重要事件。与 `chatDialog` 不同,它**不阻断操作流程**,支持自动消失和自定义按钮。
#### 文件位置
```
resources/views/chat/partials/scripts.blade.php ← chatBanner 定义window.chatBanner
app/Events/BannerNotification.php ← 广播事件(后端推送)
app/Http/Controllers/Admin/BannerBroadcastController.php ← 管理员推送接口
```
#### 前端 API
```javascript
window.chatBanner.show(options); // 显示大卡片
window.chatBanner.close(id); // 关闭指定 banner
```
#### `show(options)` 参数说明
| 参数 | 类型 | 说明 | 默认值 |
| ------------ | ---------- | ------------------------------------------- | --------------------------------- |
| `id` | `string` | 可选banner DOM ID同 ID 自动覆盖防重叠 | `'chat-banner-default'` |
| `icon` | `string` | 顶部 Emoji 图标 | 无 |
| `title` | `string` | 小标题(显示为 `══ 标题 ══` | 无 |
| `name` | `string` | 大名字/主角行(自动 HTML 转义) | 无 |
| `body` | `string` | 主内容,支持有限 HTML`<b><span><br>` 等) | 无 |
| `sub` | `string` | 副说明(小字,支持有限 HTML | 无 |
| `gradient` | `string[]` | 渐变色数组,如 `['#4f46e5', '#db2777']` | `['#4f46e5','#7c3aed','#db2777']` |
| `titleColor` | `string` | 小标题字体颜色 | `'#fde68a'` |
| `autoClose` | `number` | 自动关闭时间ms`0` = 不自动关闭 | `5000` |
| `buttons` | `Button[]` | 按钮列表(见下) | 无(无按钮时不可点击) |
`buttons` 数组元素:
```typescript
{
label: string, // 按钮文字
color: string, // 背景色CSS 颜色值)
onClick: (btn: HTMLElement, close: () => void) => void, // 点击回调
}
```
#### 使用示例
```javascript
// ① 简单通知5秒自动消失
window.chatBanner.show({
icon: "🎉💚🎉",
title: "好友通知",
name: "lkddi1",
body: "将你加为好友了!",
sub: '<strong style="color:#a7f3d0;">你们现在互为好友 🎊</strong>',
gradient: ["#065f46", "#059669", "#10b981"],
titleColor: "#a7f3d0",
autoClose: 5000,
});
// ② 带操作按钮(不自动关闭)
window.chatBanner.show({
id: "friend-add-banner",
icon: "💚📩",
title: "好友申请",
name: "lkddi1",
body: "将你加为好友了!",
sub: "但你还没有回加对方为好友",
gradient: ["#1e3a5f", "#1d4ed8", "#0891b2"],
titleColor: "#bae6fd",
autoClose: 0, // 不自动关闭,等待用户操作
buttons: [
{
label: " 回加好友",
color: "#10b981",
onClick: (btn, close) => {
btn.textContent = "处理中…";
// ... 调用 API ...
close(); // 完成后手动关闭
},
},
{
label: "稍后再说",
color: "rgba(255,255,255,0.15)",
onClick: (btn, close) => close(),
},
],
});
// ③ 手动关闭指定 banner
window.chatBanner.close("friend-add-banner");
```
#### 后端推送(管理员)
通过 `BannerNotification` 事件向**单个用户**或**整个房间**广播大卡片:
```php
use App\Events\BannerNotification;
// 推给单个用户(私有频道 user.{username}
broadcast(new BannerNotification(
target: 'user',
targetId: 'lkddi',
options: [
'icon' => '📢',
'title' => '系统通知',
'body' => '你有一条新消息',
'gradient' => ['#4f46e5', '#7c3aed'],
'autoClose' => 6000,
]
));
// 推给整个房间Presence 频道 room.{id}
broadcast(new BannerNotification(
target: 'room',
targetId: 1,
options: [
'icon' => '🎊',
'title' => '活动公告',
'body' => '双倍积分活动已开始!',
'gradient' => ['#065f46', '#059669'],
'autoClose' => 8000,
]
));
```
也可通过管理员 HTTP 接口(仅超级管理员):
```
POST /admin/banner/broadcast
Content-Type: application/json
{
"target": "room",
"target_id": 1,
"options": {
"icon": "📢",
"title": "公告",
"body": "服务器将于 10 分钟后重启",
"gradient": ["#374151", "#4b5563"],
"autoClose": 10000
}
}
```
#### 渐变配色速查
| 场景 | 渐变数组 |
| ----------- | --------------------------------------------- |
| 好友 / 成功 | `['#065f46', '#059669', '#10b981']`(绿色) |
| 申请 / 信息 | `['#1e3a5f', '#1d4ed8', '#0891b2']`(蓝色) |
| 任命 / 荣耀 | `['#4f46e5', '#7c3aed', '#db2777']`(紫粉) |
| 撤销 / 中性 | `['#374151', '#4b5563', '#6b7280']`(灰色) |
| 警告 / 紧急 | `['#7f1d1d', '#991b1b', '#dc2626']`(红色) |
| 系统 / 特权 | `['#1e1b4b', '#312e81', '#4338ca']`(深蓝紫) |
#### 安全说明
> [!IMPORTANT]
>
> - **前端控制台调用** `window.chatBanner.show()` 只影响**用户自己的浏览器**,无法推送给他人。
> - **HTTP 推送接口** `POST /admin/banner/broadcast` 受三层中间件保护(`chat.auth` + `chat.has_position` + `chat.level:super`),普通用户调用返回 403。
> - **内容净化**`body`、`sub`、`title` 字段经过 `strip_tags` 白名单处理(允许 `<b><strong><em><span><br>`),防止 XSS。
> - **按钮 action 白名单**:仅允许 `close | add_friend | remove_friend | link`,禁止任意 JS 注入。
---
## 八、常用命令速查
```bash
# 创建 Model + Migration-m 同时生成迁移)
php artisan make:model Room -m
# 创建 Controller-r 生成 RESTful 方法)
php artisan make:controller ChatController
# 创建广播 Event
php artisan make:event MessageSent
# 创建队列 Job
php artisan make:job SaveMessageJob
# 创建 Middleware
php artisan make:middleware ChatAuthenticate
# 创建 Form Request
php artisan make:request SendMessageRequest
# 重置迁移(开发阶段)
php artisan migrate:fresh --seed
# 查看路由列表
php artisan route:list --columns=method,uri,name,action
# 代码格式化(提交前必须运行)
vendor/bin/pint --dirty
# Horizon 队列监控(生产环境)
php artisan horizon
# 重启 Horizon更新代码后
php artisan horizon:terminate
# 清理所有缓存
php artisan optimize:clear
```
---
> **参照原项目**`/Users/pllx/Web/chat/hp0709/`VBScript ASP 源码,仅作功能参考和备查,不做数据迁移)