Files
chatroom/app/Http/Controllers/Admin/BannerBroadcastController.php
lkddi 5c53b8cf2f 功能:window.chatBanner 全局大卡片公共组件
前端:
- window.chatBanner.show(options) 全局 API,完全自定义:
  icon/title/name/body/sub/gradient/titleColor/autoClose/buttons
- window.chatBanner.close(id) 关闭指定 banner
- showFriendBanner / showAppointmentBanner 均改用 chatBanner 实现
- setupBannerNotification() 监听私有+房间频道的 BannerNotification 事件

后端:
- BannerNotification 事件(ShouldBroadcastNow),支持 user/room 双目标
- BannerBroadcastController(仅超级管理员路由,三层中间件保护)
- 内容字段 strip_tags 净化防 XSS,按钮 action 白名单校验

安全:
- window.chatBanner.show() 被人控制台调用只影响自己,无法推给他人
- HTTP 入口 POST /admin/banner/broadcast 仅超管可访问
2026-03-01 01:28:23 +08:00

83 lines
3.2 KiB
PHP
Raw 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.
<?php
/**
* 文件功能:管理员大卡片通知广播控制器
*
* 仅超级管理员chat.level:super 中间件保护)可调用此接口,
* 通过 BannerNotification 事件向指定用户或房间推送自定义大卡通知。
*
* 安全保证:
* - 路由被 ['chat.auth', 'chat.has_position', 'chat.level:super'] 三层中间件保护
* - 普通用户无权访问此接口,无法伪造对他人的广播
* - options 中的用户输入字段在后端经过 strip_tags 清洗
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Http\Controllers\Admin;
use App\Events\BannerNotification;
use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class BannerBroadcastController extends Controller
{
/**
* 向指定目标广播大卡片通知。
*
* 请求参数:
* - target: 'user' | 'room'
* - target_id: 用户名 或 房间 ID
* - options: 与 window.chatBanner.show() 参数相同的对象
* - icon, title, name, body, sub, gradient(array), titleColor, autoClose, buttons(array)
*/
public function send(Request $request): JsonResponse
{
$validated = $request->validate([
'target' => ['required', 'in:user,room'],
'target_id' => ['required'],
'options' => ['required', 'array'],
'options.icon' => ['nullable', 'string', 'max:20'],
'options.title' => ['nullable', 'string', 'max:50'],
'options.name' => ['nullable', 'string', 'max:100'],
'options.body' => ['nullable', 'string', 'max:500'],
'options.sub' => ['nullable', 'string', 'max:200'],
'options.gradient' => ['nullable', 'array', 'max:5'],
'options.titleColor' => ['nullable', 'string', 'max:30'],
'options.autoClose' => ['nullable', 'integer', 'min:0', 'max:30000'],
'options.buttons' => ['nullable', 'array', 'max:4'],
]);
// 对可能包含用户输入的字段进行 HTML 净化(防 XSS
$opts = $validated['options'];
foreach (['title', 'name', 'body', 'sub'] as $field) {
if (isset($opts[$field])) {
$opts[$field] = strip_tags($opts[$field], '<b><strong><em><span><br>');
}
}
// 按钮 label 不允许 HTML
if (! empty($opts['buttons'])) {
$opts['buttons'] = array_map(function ($btn) {
$btn['label'] = strip_tags($btn['label'] ?? '');
$btn['color'] = preg_replace('/[^a-z0-9#(),\s.%rgba\/]/i', '', $btn['color'] ?? '#10b981');
// action 只允许预定义值,防止注入任意 JS
$btn['action'] = in_array($btn['action'] ?? '', ['close', 'add_friend', 'remove_friend', 'link'])
? $btn['action'] : 'close';
return $btn;
}, $opts['buttons']);
}
broadcast(new BannerNotification(
target: $validated['target'],
targetId: $validated['target_id'],
options: $opts,
));
return response()->json(['status' => 'success', 'message' => '广播已发送']);
}
}