83 lines
3.2 KiB
PHP
83 lines
3.2 KiB
PHP
|
|
<?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' => '广播已发送']);
|
|||
|
|
}
|
|||
|
|
}
|