From bcb762df779695fc0f96127cdaafa33057752814 Mon Sep 17 00:00:00 2001 From: lkddi Date: Mon, 27 Apr 2026 11:23:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=95=86=E5=BA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/phase1-decoration-plan.md | 448 +++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 docs/phase1-decoration-plan.md diff --git a/docs/phase1-decoration-plan.md b/docs/phase1-decoration-plan.md new file mode 100644 index 0000000..5b1edd5 --- /dev/null +++ b/docs/phase1-decoration-plan.md @@ -0,0 +1,448 @@ +# 第一阶段:消息装扮 & 头像框消费系统 — 实施方案 + +> 目标:为聊天室新增持续性的金币消费出口,通过「消息装扮」和「头像框」两个高社交可见度的功能,让金币有处可花、花得有面子。 + +--- + +## 一、功能范围 + +### 1.1 消息气泡样式(msg_bubble) +用户购买后,发出的消息气泡带特殊边框/背景样式,房间内所有人可见。 + +| 商品 | 价格 | 时长 | 效果描述 | +|------|------|------|----------| +| 金色气泡 | 300 金币 | 1 天 | 消息框带金色渐变边框 + 微光 | +| 樱花气泡 | 500 金币 | 3 天 | 粉白边框 + 飘落花瓣 | +| 星际气泡 | 800 金币 | 7 天 | 深蓝渐变边框 + 星光 | +| 彩虹气泡 | 1500 金币 | 7 天 | 流动彩虹边框 | +| 皇冠气泡 | 3000 金币 | 30 天 | 皇家风格金边 + 皇冠徽标 | + +### 1.2 昵称颜色效果(msg_name_color) +购买后,自己的昵称在聊天消息和用户列表中显示为特殊颜色。 + +| 商品 | 价格 | 时长 | 效果描述 | +|------|------|------|----------| +| 金色昵称 | 200 金币 | 1 天 | 昵称文字变为金色 #fbbf24 | +| 渐变色昵称 | 500 金币 | 3 天 | 彩虹渐变色(CSS gradient text) | +| 发光昵称 | 800 金币 | 7 天 | 文字发光效果(text-shadow glow) | +| 火焰昵称 | 1500 金币 | 7 天 | 红橙火焰色 + 脉动动画 | + +### 1.3 头像框(avatar_frame) +购买后,用户头像外围显示装饰边框,在用户列表中展示。 + +| 商品 | 价格 | 时长 | 效果描述 | +|------|------|------|----------| +| 银色边框 | 500 金币 | 7 天 | 银色圆环边框 | +| 金色边框 | 1000 金币 | 7 天 | 金色圆环边框 | +| 星光边框 | 2000 金币 | 14 天 | 星光闪烁旋转边框 | +| 龙纹边框 | 5000 金币 | 30 天 | 龙纹缠绕高级边框 | + +--- + +## 二、架构设计 + +### 2.1 整体思路 + +遵循项目现有的 `ShopService → UserCurrencyService` 消费模式,最小化架构改动: + +``` +用户点击购买 → ShopController::buy() + → ShopService::buyItem() [新增 msg_bubble / msg_name_color / avatar_frame 分支] + → DB::transaction: + ① UserCurrencyService::change() 扣金币 + 写流水 + ② UserPurchase::create() 写购买记录 + ③ 更新 users.active_decorations JSON 字段 + → 返回购买结果 + 新余额 + +发送消息时 → ChatController::send() + → 读取 users.active_decorations + → 写入 messageData 广播 payload + +用户列表 → ChatUserPresenceService::build() + → 读取 users.active_decorations.avatar_frame + → 写入 presence payload +``` + +### 2.2 数据存储方案 + +**不使用新表**,在现有两张表上扩展: + +**`users` 表** — 新增 1 个字段用于缓存当前激活的装扮(避免每次发消息都 JOIN user_purchases): + +```sql +ALTER TABLE users ADD COLUMN active_decorations JSON NULL + COMMENT '当前激活的装扮,格式: {"bubble":{"style":"golden","expires_at":"..."},"name_color":{...},"avatar_frame":{...}}'; +``` + +示例数据: +```json +{ + "bubble": {"style": "golden", "expires_at": "2026-05-04T12:00:00+08:00"}, + "name_color": {"style": "rainbow", "expires_at": "2026-05-01T12:00:00+08:00"}, + "avatar_frame": {"style": "dragon", "expires_at": "2026-05-27T12:00:00+08:00"} +} +``` + +**`shop_items` 表** — 新增商品行,利用现有的 `type`、`duration_days` 字段(无需改表结构)。 + +**`user_purchases` 表** — 利用现有表记录所有购买历史(无需改表结构)。 + +### 2.3 装扮过期策略 + +**懒过期(Lazy Expiration)**:每次读取 `active_decorations` 时检查过期时间,自动清理。 + +在以下时机触发检查和清理: +- 用户发送消息时(`ChatController::send()`) +- 构建在线用户 payload 时(`ChatUserPresenceService::build()`) +- 查询商店数据时(`ShopController::items()`) + +过期清理逻辑在一个统一的 helper 方法中: +```php +// DecorationService::getActiveDecorations(User $user): array +// 读取 users.active_decorations JSON +// 过滤掉已过期的条目 +// 如果有变化,写回 users.active_decorations +// 返回干净的激活装扮列表 +``` + +--- + +## 三、后端改动清单 + +### 3.1 数据库 Migration + +**新文件**:`database/migrations/xxxx_xx_xx_add_active_decorations_to_users_table.php` + +```php +Schema::table('users', function (Blueprint $table) { + $table->json('active_decorations')->nullable() + ->comment('当前激活的装扮: bubble/name_color/avatar_frame'); +}); +``` + +### 3.2 CurrencySource 枚举 + +**文件**:`app/Enums/CurrencySource.php` — 新增两个 case: + +```php +/** 购买消息装扮(气泡/昵称颜色等) */ +case MSG_DECORATION_BUY = 'msg_decoration_buy'; + +/** 购买头像框 */ +case AVATAR_FRAME_BUY = 'avatar_frame_buy'; +``` + +同时在 `label()` 方法中添加对应的中文映射。 + +### 3.3 DecorationService(新服务) + +**文件**:`app/Services/DecorationService.php` + +```php +class DecorationService +{ + /** + * 购买装扮:扣金币 + 写记录 + 更新 active_decorations + */ + public function purchase(User $user, ShopItem $item): array; + + /** + * 获取用户当前激活的装扮(自动清理过期项) + * 返回: ['bubble' => [...], 'name_color' => [...], 'avatar_frame' => [...]] + */ + public function getActiveDecorations(User $user): array; + + /** + * 判断装扮类型之间是否互斥(同类型新购买覆盖旧的) + */ + public function isSameCategory(string $typeA, string $typeB): bool; +} +``` + +**`purchase()` 核心逻辑**: +1. 计算总价(`$item->price`) +2. 检查金币余额 +3. `DB::transaction`: + - 通过 `UserCurrencyService` 扣金币 + - 创建 `UserPurchase` 记录(status=active, expires_at=now+days) + - 读取当前 `active_decorations`,同类型覆盖写入,不同类型合并 + - `$user->active_decorations = $merged; $user->save();` +4. 返回结果 + +**`getActiveDecorations()` 核心逻辑**: +1. 读取 `$user->active_decorations`(JSON → array) +2. 遍历每个 slot,检查 `expires_at` 是否过期 +3. 如果有过期项被清理,写回数据库 +4. 返回干净的数组 + +### 3.4 ShopService 扩展 + +**文件**:`app/Services/ShopService.php` + +在 `buyItem()` 的 `match ($item->type)` 中新增三个分支: + +```php +'msg_bubble' => $this->decorationService->purchase($user, $item), +'msg_name_color' => $this->decorationService->purchase($user, $item), +'avatar_frame' => $this->decorationService->purchase($user, $item), +``` + +注入 `DecorationService` 依赖。 + +### 3.5 ShopController 扩展 + +**文件**:`app/Http/Controllers/ShopController.php` + +在 `items()` 方法的返回数据中增加: + +```php +'active_decorations' => $this->decorationService->getActiveDecorations($user), +``` + +### 3.6 消息广播增强 + +**文件**:`app/Http/Controllers/ChatController.php` + +在 `send()` 方法中,构造 `$messageData` 时增加装饰字段: + +```php +$decorations = app(DecorationService::class)->getActiveDecorations($user); + +// 在 $messageData 中追加 +if (!empty($decorations['bubble'])) { + $messageData['msg_bubble'] = $decorations['bubble']['style']; +} +if (!empty($decorations['name_color'])) { + $messageData['msg_name_color'] = $decorations['name_color']['style']; +} +``` + +这样每条消息的 WebSocket 广播 payload 中就带上了装扮信息。 + +### 3.7 在线用户展示增强 + +**文件**:`app/Services/ChatUserPresenceService.php` + +在 `build()` 方法的 $payload 数组中增加: + +```php +$decorations = app(DecorationService::class)->getActiveDecorations($user); +if (!empty($decorations['avatar_frame'])) { + $payload['avatar_frame'] = $decorations['avatar_frame']['style']; +} +if (!empty($decorations['name_color'])) { + $payload['name_color'] = $decorations['name_color']['style']; +} +``` + +### 3.8 Shop Seeder 扩展 + +**文件**:`database/seeders/ShopItemSeeder.php` + +新增装扮商品数据(sort_order 50-80 范围,排在改名卡之后)。 + +### 3.9 User Model + +**文件**:`app/Models/User.php` + +在 `$fillable` 和 `$casts` 中增加: + +```php +'active_decorations' => 'array', // 已在 $casts 中 +``` + +--- + +## 四、前端改动清单 + +### 4.1 商店面板改造 + +**文件**:`resources/js/chat-room/shop-controls.js` + +1. 在 `SHOP_GROUPS` 数组中新增三个分组: + ```js + { label: "💬 消息气泡", type: "msg_bubble" }, + { label: "🎨 昵称颜色", type: "msg_name_color" }, + { label: "🖼️ 头像框", type: "avatar_frame" }, + ``` + +2. 渲染装扮卡片时显示: + - 当前是否已激活同类装扮 + - 剩余有效时间 + - 购买/续费按钮 + +3. 购买成功后更新本地装扮状态,无需刷新整个列表。 + +**文件**:`resources/views/chat/partials/shop-panel.blade.php` + +可能需要微调样式以适配新卡片。 + +### 4.2 消息渲染改造 + +**文件**:`resources/views/chat/partials/scripts.blade.php` + +在 `appendMessage()` 函数中(约第 2105 行附近),根据消息 payload 中的装扮字段应用样式: + +```javascript +// 在构建消息 HTML 时 +let bubbleClass = ''; +let nameColorStyle = ''; + +if (msg.msg_bubble) { + bubbleClass = `msg-bubble--${msg.msg_bubble}`; +} +if (msg.msg_name_color) { + nameColorStyle = `style="color: var(--name-${msg.msg_name_color})"`; +} + +// 消息容器加上 bubbleClass +// 发送者名字处加上 nameColorStyle +``` + +**新增 CSS 样式**(可放在 `scripts.blade.php` 的 `