bootdesk/chat-sdk-laravel 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

bootdesk/chat-sdk-laravel

Composer 安装命令:

composer require bootdesk/chat-sdk-laravel

包简介

Laravel integration for the PHP Chat SDK

README 文档

README

Laravel integration for laravel-bootdesk.

Install

composer require bootdesk/chat-sdk-laravel

Setup

php artisan chat:install

This publishes config/chat.php to your application.

Configuration

The published config/chat.php file contains the following sections:

return [

    // The display name your bot uses when posting messages.
    'user_name' => env('BOT_USERNAME', 'Bot'),

    // Platform adapters to enable. Only adapters whose Composer package
    // is installed (class_exists) will be loaded. For multi-tenant
    // setups, omit the platform here and use an AdapterResolver instead.
    'adapters' => [
        // 'slack' => [
        //     'bot_token' => env('SLACK_BOT_TOKEN'),
        //     'signing_secret' => env('SLACK_SIGNING_SECRET'),
        // ],
        // 'telegram' => [
        //     'bot_token' => env('TELEGRAM_BOT_TOKEN'),
        // ],
        // 'whatsapp' => [
        //     'access_token' => env('WHATSAPP_ACCESS_TOKEN'),
        //     'app_secret' => env('WHATSAPP_APP_SECRET'),
        //     'phone_number_id' => env('WHATSAPP_PHONE_NUMBER_ID'),
        //     'verify_token' => env('WHATSAPP_VERIFY_TOKEN'),
        // ],
        // 'discord' => [
        //     'bot_token' => env('DISCORD_BOT_TOKEN'),
        //     'application_id' => env('DISCORD_APPLICATION_ID'),
        //     'public_key' => env('DISCORD_PUBLIC_KEY'),
        // ],
        // 'messenger' => [
        //     'page_access_token' => env('MESSENGER_PAGE_ACCESS_TOKEN'),
        //     'app_secret' => env('MESSENGER_APP_SECRET'),
        //     'verify_token' => env('MESSENGER_VERIFY_TOKEN'),
        // ],
        // 'web' => [
        //     'user_name' => env('BOT_USERNAME', 'Bot'),
        // ],
        // 'github' => [
        //     'auth_token' => env('GITHUB_TOKEN'),
        //     'webhook_secret' => env('GITHUB_WEBHOOK_SECRET'),
        // ],
        // 'linear' => [
        //     'api_key' => env('LINEAR_API_KEY'),
        //     'webhook_secret' => env('LINEAR_WEBHOOK_SECRET'),
        // ],
    ],

    // State persistence backed by the Laravel Cache facade. The cache
    // store is resolved from the facade at runtime — configure it in
    // config/cache.php as usual.
    'state' => [
        'prefix' => env('CHAT_STATE_PREFIX', 'chat:'),
    ],

    // Global handler classes registered on every Chat instance regardless
    // of adapter. Each class must implement a register($chat) method.
    'handlers' => [
        // \App\Chat\GlobalHandlers::class,
    ],

    // Adapter-specific handler groups. Only the matching group is
    // registered per webhook request, alongside global handlers above.
    'handler_groups' => [
        // 'slack' => [
        //     \App\Chat\SlackHandler::class,
        // ],
        // 'telegram' => [
        //     \App\Chat\TelegramHandler::class,
        // ],
    ],

    // How to handle concurrent messages for the same thread.
    // Core strategies: drop (default), queue, debounce, concurrent.
    // Laravel uses QueueConcurrencyHandler to dispatch jobs for async processing.
    'concurrency' => env('CHAT_CONCURRENCY', 'drop'),

    // Scope for distributed locks: 'thread' (default) or 'channel'.
    // Use 'channel' for platforms like WhatsApp/Telegram where
    // conversations are per-channel (one conversation per phone number).
    'lock_scope' => env('CHAT_LOCK_SCOPE', 'thread'),

    // Cross-platform per-user message persistence. Requires an
    // IdentityResolver bound to the container (IdentityResolver::class).
    'transcripts' => null,

];

Webhook Routes

Register a webhook route for incoming platform events:

// routes/web.php or routes/api.php
use BootDesk\ChatSDK\Laravel\Http\Controllers\WebhookController;

Route::match(['get', 'post'], '/api/webhooks/{adapter}', WebhookController::class);

The {adapter} segment matches the keys in your config/chat.php adapters array (e.g. slack, telegram, discord).

Handlers

Create a handler class to respond to messages:

// app/Chat/ChatHandlers.php
namespace App\Chat;

use BootDesk\ChatSDK\Core\Chat;
use BootDesk\ChatSDK\Core\MessageContext;
use BootDesk\ChatSDK\Laravel\Contracts\ChatHandler as ChatHandlerContract;

class ChatHandlers implements ChatHandlerContract
{
    public function register(Chat $chat): void
    {
        $chat->onNewMessage('/^hello$/i', function (MessageContext $ctx) {
            $ctx->thread->post('Hey!');
        });

        $chat->fallback(function (MessageContext $ctx) {
            $ctx->thread->post("I don't understand that.");
        });
    }
}

Register it in config/chat.php:

// Global — fires for every adapter
'handlers' => [\App\Chat\ChatHandlers::class],

Or scoped to a specific adapter group:

'handler_groups' => [
    'slack' => [\App\Chat\SlackHandlers::class],
    'telegram' => [\App\Chat\TelegramHandlers::class],
],

When a webhook arrives for slack, both global and slack group handlers are registered. telegram group handlers are skipped.

Multiple Groups per Channel

Override resolveGroups on the WebhookController to route channels to different handler groups — even combining multiple groups:

use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface;

class ChannelAwareController extends WebhookController
{
    protected function resolveGroups(string $adapter, Request $request, ServerRequestInterface $psrRequest): array
    {
        $channel = $request->input('channel_id');

        return match ($channel) {
            'C001' => ['slack', 'internal-support'],
            'C002' => ['slack', 'customer-support'],
            default => [$adapter],
        };
    }
}

Then ChatFactory::forGroups(['slack', 'internal-support']) merges global handlers + handlers from both groups. Groups are serialized as the chat_groups PSR-7 attribute — they survive into async queue jobs automatically.

Handlers that Inspect the Request

Implement ChatHandlerWithRequest to access the PSR-7 request during registration:

use BootDesk\ChatSDK\Laravel\Contracts\ChatHandlerWithRequest;

class TenantAwareHandler implements ChatHandlerWithRequest
{
    public function register(Chat $chat, ?ServerRequestInterface $request = null): void
    {
        $tenant = $request?->getHeaderLine('X-Tenant') ?? 'default';

        $chat->onNewMessage('/bill/', function (MessageContext $ctx) use ($tenant) {
            // tenant-specific billing flow
        });
    }
}

The factory auto-detects which interface the handler implements — existing ChatHandler implementations continue working unchanged.

Middleware

Middleware intercept messages at different stages. Register in your handler class:

use BootDesk\ChatSDK\Core\Contracts\WebhookMiddleware;
use BootDesk\ChatSDK\Core\Contracts\ReceivingMiddleware;
use BootDesk\ChatSDK\Core\Contracts\SendingMiddleware;

class ChatHandlers
{
    public function register(Chat $chat): void
    {
        // Intercept raw webhook before parsing
        $chat->addWebhookMiddleware(new class implements WebhookMiddleware {
            public function handle(ServerRequestInterface $request, callable $next): ResponseInterface {
                logger()->info('Webhook received', ['path' => $request->getUri()->getPath()]);
                return $next($request);
            }
        });

        // Transform inbound messages before handlers
        $chat->addReceivingMiddleware(new class implements ReceivingMiddleware {
            public function handle(Message $message, Adapter $adapter, callable $next): ?Message {
                // Return null to drop the message
                if (str_contains($message->text, 'blocked')) {
                    return null;
                }
                return $next($message);
            }
        });

        // Transform outbound messages before delivery
        $chat->addSendingMiddleware(new class implements SendingMiddleware {
            public function handle(string $threadId, PostableMessage $message, Adapter $adapter, string $operation, callable $next): ?SentMessage {
                logger()->info('Sending message', ['thread' => $threadId, 'operation' => $operation]);
                return $next($message);
            }
        });
    }
}

Operations: post, edit, postEphemeral

Transcripts

Per-user message persistence stored via the Laravel cache. Incoming and outgoing messages are auto-recorded when configured.

Setup

Bind an IdentityResolver and set transcripts config:

// AppServiceProvider::register()
use BootDesk\ChatSDK\Core\Contracts\IdentityResolver;
use BootDesk\ChatSDK\Core\Author;

$this->app->bind(IdentityResolver::class, fn () => new class implements IdentityResolver {
    public function resolve(Author $author): ?string {
        return $author->id;
    }
});
// config/chat.php
'transcripts' => ['max_messages' => 100, 'ttl_ms' => 2592000000],

Custom implementation

Override the TranscriptsApi binding in any service provider:

$this->app->bind(TranscriptsApi::class, function ($app) {
    return new MyRedisTranscriptsApi(
        state: $app->make(StateAdapter::class),
        config: ['max_messages' => 200],
    );
});

Usage

$transcripts = $chat->getTranscripts();

// List history for a user
$entries = $transcripts->list('user:U123');

// Each entry has: id, text, authorId, threadId, timestamp, direction

// Count
$count = $transcripts->count('user:U123');

// Delete
$transcripts->delete('user:U123');

Multi-Tenant Adapter Resolution

For multi-tenant applications where each tenant has their own bot credentials, use an AdapterResolver:

// app/Chat/MultiTenantAdapterResolver.php
namespace App\Chat;

use BootDesk\ChatSDK\Core\Contracts\Adapter;
use BootDesk\ChatSDK\Core\Contracts\AdapterResolver;
use BootDesk\ChatSDK\Slack\SlackAdapter;
use BootDesk\ChatSDK\Telegram\TelegramAdapter;
use Illuminate\Support\Facades\DB;
use Psr\Http\Message\ServerRequestInterface;

class MultiTenantAdapterResolver implements AdapterResolver
{
    public function resolve(string $name, ?ServerRequestInterface $request): ?Adapter
    {
        // Extract tenant from request (header, subdomain, route param, etc.)
        // When called from a job, $request is null - use other context (job payload, auth, etc.)
        $tenantId = $request?->getHeaderLine('X-Tenant-ID')
            ?? $this->getTenantFromContext();

        if ($tenantId === null || $tenantId === '') {
            return null;
        }

        // Load tenant-specific credentials from database
        $config = DB::table('tenant_chat_configs')
            ->where('tenant_id', $tenantId)
            ->where('adapter', $name)
            ->first();

        if (! $config) {
            return null;
        }

        // Instantiate adapter with tenant credentials
        return match ($name) {
            'slack' => new SlackAdapter(
                botToken: $config->credentials['bot_token'],
                httpClient: app(\Psr\Http\Client\ClientInterface::class),
                signingSecret: $config->credentials['signing_secret'] ?? null,
            ),
            'telegram' => new TelegramAdapter(
                botToken: $config->credentials['bot_token'],
                httpClient: app(\Psr\Http\Client\ClientInterface::class),
                secretToken: $config->credentials['secret_token'] ?? null,
            ),
            default => null,
        };
    }
}

Register the resolver in a service provider:

// app/Providers/AppServiceProvider.php
use BootDesk\ChatSDK\Core\Contracts\AdapterResolver;

public function register(): void
{
    $this->app->bind(
        AdapterResolver::class,
        \App\Chat\MultiTenantAdapterResolver::class
    );
}

Resolution order: Tenant-specific (resolver) → Global (config). Tenants can override specific adapters while falling back to global defaults for others.

Injecting ChatFactory

To send messages programmatically, inject ChatFactory and get a Chat instance:

use BootDesk\ChatSDK\Laravel\ChatFactory;

class MessageController
{
    public function __construct(
        private ChatFactory $chatFactory,
    ) {}

    public function send()
    {
        $chat = $this->chatFactory->default(); // global handlers only
        $chat->thread('slack:C123')->post('Hello!');
    }
}

Or for adapter-specific handlers:

$chat = $this->chatFactory->forGroup('slack'); // global + slack handlers
$chat->handleWebhook('slack', $psrRequest);

For multiple groups:

$chat = $this->chatFactory->forGroups(['slack', 'internal-support']); // global + both groups

Artisan Commands

Command Description
php artisan chat:list List configured adapters
php artisan chat:install Publish config file

Queue Processing

The package binds QueueConcurrencyHandler as the default ConcurrencyHandler. It dispatches jobs as follows: drop acquires a lock during the webhook (dispatches ProcessMessageJob if acquired, drops silently if held — lock released when job finishes); queue and concurrent dispatch ProcessMessageJob; debounce dispatches ProcessDebouncedMessageJob (unique delayed job). The debounce job caches the latest message and a :last timestamp; on re-dispatch it does not restore :last — preventing infinite re-dispatch loops. :latest and :skipped restoration is guarded against overwriting concurrent webhook data. RequiresSyncResponse adapters always process inline (within the HTTP request) regardless of strategy.

When the original PSR-7 webhook request is available, QueueConcurrencyHandler serializes it into a RequestContext value object (method, URI, headers, body, query/parsed/server params, cookies, version, requestAttributes) and passes it to every dispatched job. Both ProcessMessageJob and ProcessDebouncedMessageJob reconstruct the PSR-7 request and pass it to Chat::resolveAdapter() — so AdapterResolver::resolve($name, $request) receives the original request in both sync and queued contexts. The requestAttributes field captures PSR-7 getAttributes() — extend WebhookController to add tenant/context attributes that survive into jobs.

Make sure your Laravel queue worker is running:

php artisan queue:work

No manual setup is needed beyond configuring your queue driver in config/queue.php.

State

State persistence uses the Laravel Cache facade. The cache store is resolved from the facade at runtime — configure it in config/cache.php as usual.

Error Handling

Adapter exceptions bubble up to Laravel's exception handler. Register custom handlers in app/Exceptions/Handler.php:

use BootDesk\ChatSDK\Core\Exceptions\AdapterException;
use BootDesk\ChatSDK\Core\Exceptions\AuthenticationException;
use BootDesk\ChatSDK\Core\Exceptions\RateLimitException;
use Illuminate\Http\Request;

public function register(): void
{
    $this->renderable(function (AuthenticationException $e, Request $request) {
        return response()->json(['error' => 'Unauthorized'], 401);
    });

    $this->renderable(function (RateLimitException $e, Request $request) {
        return response()->json(['error' => 'Rate limited'], 429);
    });

    $this->renderable(function (AdapterException $e, Request $request) {
        Log::error('Chat adapter error', [
            'message' => $e->getMessage(),
            'adapter' => $request->route('adapter'),
        ]);

        return response()->json(['error' => 'Adapter failed'], 500);
    });
}

Exception types:

  • AuthenticationException — Invalid credentials/tokens
  • RateLimitException — Platform rate limit exceeded
  • AdapterException — Generic adapter errors
  • ResourceNotFoundException — Adapter/thread not found
  • ValidationException — Invalid input data

Documentation

Full API documentation: https://bootdesk.github.io/chat-sdk

License

MIT

bootdesk/chat-sdk-laravel 适用场景与选型建议

bootdesk/chat-sdk-laravel 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 233 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 05 月 18 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 bootdesk/chat-sdk-laravel 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 bootdesk/chat-sdk-laravel 我们能提供哪些服务?
定制开发 / 二次开发

基于 bootdesk/chat-sdk-laravel 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 233
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 26
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-05-18