Skip to content
Back to skills

Realtime Support Chat Widget

ASecurity

当需要在应用内搭建用户端浮动客服窗口 + 管理端工作台的实时聊天系统时使用;做数据模型、REST 接口、WebSocket 频道、前端组件、延时邮件通知的端到端落地方案与代码;不适用于群聊/多人房间、AI 自动应答机器人、纯邮件工单系统。触发词:在线客服、实时聊天组件、客服工作台

  • 3 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 19, 2026
ai-agentstypescriptgorubysqlreactvuenextjsnoderailsaws

Works with

  • cursor
  • cli
  • api

Security analysis

A100/100

Scanned September 19, 2026

npx -y skills add findscripter/everything-skills --skill realtime-support-chat-widget --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Realtime Support Chat Widget?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Realtime Support Chat Widget
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/findscripter-realtime-support-chat-widget/badge)](https://www.skillsdirectory.com/skills/findscripter-realtime-support-chat-widget)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: realtime-support-chat-widget
title: 实时客服聊天组件系统
description: 当需要在应用内搭建用户端浮动客服窗口 + 管理端工作台的实时聊天系统时使用;做数据模型、REST 接口、WebSocket 频道、前端组件、延时邮件通知的端到端落地方案与代码;不适用于群聊/多人房间、AI 自动应答机器人、纯邮件工单系统。触发词:在线客服、实时聊天组件、客服工作台
domain: 研发/architecture
triggers: [在线客服, 实时聊天组件, 客服工作台, 应用内支持聊天, 浮动聊天窗口, WebSocket 消息推送, 用户与管理员实时消息, live chat widget]
tags: [实时通信, websocket, 客服系统, 全栈, 前端组件, 邮件通知]
level: 进阶
status: stable
agents: [claude-code, codex, cursor, gemini-cli]
tools: [WebSocket/ActionCable/Pusher/Ably, REST API, 关系型数据库(PostgreSQL/MySQL), 后台任务队列, 事务邮件服务(Postmark/SendGrid/SES/Resend)]
requires: []
related: [websocket-realtime-engineer, rest-api-endpoint-builder, transactional-email-template-builder, ai-customer-support]
combines_with: [database-design-advisor, react-state-management, support-ticket-triage]
license: MIT
source: sickn33/agentic-awesome-skills
source_license: MIT
---
## 何时使用

需要在产品里嵌入「实时客服聊天」时使用,典型诉求:

- 给应用加一个右下角浮动聊天窗口(用户端)。
- 搭建客服/管理员工作台,集中查看、回复、归档会话。
- 实现用户与客服之间的双向实时消息推送。
- 提供应用内支持渠道,并在客服回复后补发邮件提醒。

不该用边界(出现以下场景请改用其他方案):

- 多人群聊 / 频道 / 房间式聊天(本方案核心约束是「每用户一会话」)。
- AI 自动应答机器人、工单分配/路由、坐席排班等复杂客服中台。
- 纯异步邮件工单,无实时性要求。
- 端到端加密 IM、音视频通话。

适用前先确认:是否有用户鉴权体系、是否能区分 `admin` 角色、运行环境是否支持 WebSocket(serverless 需走 Pusher/Ably 等托管方案)。缺少必要输入或边界不清时先停下来澄清。

## 步骤

整体架构:前端分「用户浮动组件」与「管理员工作台」两端,经 WebSocket(实时)+ REST(拉取/状态变更)连后端;后端含两个频道(每会话 ChatChannel、全局 AdminNotificationChannel)、两张表(Chat、Message)与一个延时邮件 Job。

### 步骤 1:数据模型

建两张表,主键推荐 UUID(不可猜测)。

`support_chats`:`user_id`(外键,UNIQUE,每用户一会话)、`last_message_at`(排序用)、`admin_viewed_at`(管理员最后查看时间)、`archived_at`(null=活跃,有值=已归档)、时间戳。

`support_messages`:`chat_id`(外键)、`content`(text,必填)、`sender_type`(枚举 `user`|`admin`)、`read_at`(null=未读)、时间戳。

关键索引:`support_chats.user_id`(unique)、`last_message_at`、`archived_at`、`support_messages.chat_id`,以及复合索引 `(chat_id, created_at)`(保证按时间排序)。

关系:`User has_one SupportChat`;`SupportChat has_many SupportMessages`。

模型方法(伪码):

```pseudo
# Chat
touch_last_message()  -> last_message_at = now()
unread_for_admin?()   -> 存在 message(sender_type='user' 且 created_at > admin_viewed_at)
mark_viewed_by_admin()-> admin_viewed_at = now()
archive() / unarchive() / archived?()  -> 操作 archived_at

# Message after_create
chat.touch_last_message()
if sender_type=='user' and chat.archived?: chat.unarchive()   # 用户来信自动复活归档会话
# after_create_commit
broadcast 到 support_chat 频道
if sender_type=='user': broadcast 到 admin 通知频道
if sender_type=='admin': 安排 5 分钟延时邮件
```

### 步骤 2:REST 接口

用户端:`GET /support_chat`(取或建当前用户会话+消息)、`PATCH /support_chat/mark_read`(标记管理员消息已读)。

管理端:`GET /admin/chats?archived=true|false`(列表)、`GET /admin/chats/:id`(详情)、`POST /admin/chats/:id/archive`、`POST /admin/chats/:id/unarchive`。

列表查询要点:按 `archived_at` 过滤、`includes(:user,:messages)` 防 N+1、`order(last_message_at desc)`;每项返回 `user_email`、末条消息预览(截断 100)、末条发送方、消息数、`unread`、`archived`。

### 步骤 3:WebSocket 频道

`ChatChannel`(每会话一条流):订阅时校验 `chat.user_id==current_user.id || current_user.is_admin`,不通过则 `reject`,通过则 `stream_from "support_chat:#{chat_id}"`;`send_message` 动作按当前角色写入 `sender_type`,空内容直接丢弃。

`AdminNotificationChannel`(全体管理员一条全局流):非 admin `reject`,否则 `stream_from "admin_support_notifications"`。

广播:消息落库后向 `support_chat:#{chat.id}` 推 `{type:"new_message", message}`;若发送方是 `user`,再向 `admin_support_notifications` 推 `{type:"new_user_message", chat_id, user_email, message}`。

### 步骤 4:用户端浮动组件

组件树:`ChatWidget` → `ChatButton`(fixed 右下角,含未读角标,封顶显示 `9+`)+ `ChatPanel`(Header 含连接状态点 / 可滚动 MessageList / InputArea)。

状态 Hook `useSupportChat`:挂载时 `fetch('/support_chat')`,把已有消息 id 灌进 `seenMessageIds`(去重用);`chat.id` 变化时订阅 ChatChannel,收到 `new_message` 先查重复 id(命中即丢弃)再入列,管理员消息播提示音;连接/断开同步 `connected`;卸载时退订。`sendMessage` 走 `subscription.perform('send_message', {content: content.trim()})`。

交互:点击切换面板;打开时自动 `markAsRead()`;新消息自动滚到底;绿点=已连接;Enter 发送、Shift+Enter 换行。样式:用户消息右对齐主色,管理员消息左对齐浅色,各带时间戳。

### 步骤 5:管理员工作台

列表页:标题「Support Chats」+ [Active]/[Archived] Tab;卡片按 `last_message_at desc`,显示未读标记、用户邮箱、末条预览、消息数+相对时间;末条来自管理员时加「You: 」前缀;点击进详情。

详情页:顶部用户邮箱 + 归档/恢复按钮 + 返回;消息按日期分组加分隔线;**方向与用户端相反**(用户左、管理员右);显示发送者标签;复用同一 WebSocket 订阅;页面加载时服务端调用 `mark_viewed_by_admin()`。

### 步骤 6:延时邮件通知

`SupportReplyNotificationJob`(管理员发消息时安排,延时 5 分钟)。守卫子句缺一不可:`sender_type != 'admin'` 跳过、`read_at != null`(已读)跳过、`chat.archived?` 跳过;其余发送邮件,正文含截断预览+打开会话链接。延时让用户先有机会在应用内看到,避免秒回刷屏轰炸。

### 步骤 7:TypeScript 类型

定义 `SupportMessage`、`SupportChat`、`SupportChatListItem`、`AdminSupportChat`,以及频道消息 `ChatChannelMessage{type:'new_message'}`、`AdminNotificationMessage{type:'new_user_message'}`。时间字段统一 ISO8601 字符串。

## 关键设计决策

1. 每用户一会话——简化 UX,历史连续。
2. 归档=软删除——保留历史、可恢复。
3. 自动复活——用户向已归档会话发消息即 unarchive。
4. 延时邮件——5 分钟防刷屏。
5. 消息去重——自己发的消息会经广播回声,靠 `seenMessageIds` 拦截。
6. 独立 admin 频道——为全局未读数、桌面通知等后续能力留口。

## 示例

Rails 端模型与频道(保留源关键实现):

```ruby
class SupportMessage < ApplicationRecord
  belongs_to :support_chat
  enum :sender_type, { user: 0, admin: 1 }
  validates :content, presence: true
  after_create :update_chat_timestamp
  after_create :auto_unarchive, if: :user?
  after_create_commit :broadcast_message
  after_create_commit :schedule_notification, if: :admin?

  def broadcast_message
    ActionCable.server.broadcast("support_chat:#{support_chat_id}",
      { type: "new_message", message: { id:, content:, sender_type:, read_at:, created_at: } })
  end
  def schedule_notification
    SupportReplyNotificationJob.set(wait: 5.minutes).perform_later(self)
  end
end

class SupportChatChannel < ApplicationCable::Channel
  def subscribed
    @chat = SupportChat.find(params[:chat_id])
    reject unless @chat.user_id == current_user.id || current_user.admin?
    stream_from "support_chat:#{@chat.id}"
  end
  def send_message(data)
    @chat.support_messages.create!(content: data["content"],
      sender_type: current_user.admin? ? :admin : :user)
  end
end
```

迁移(注意复合索引):

```ruby
create_table :support_messages, id: :uuid do |t|
  t.references :support_chat, type: :uuid, null: false, foreign_key: true
  t.text :content, null: false
  t.integer :sender_type, default: 0
  t.datetime :read_at
  t.timestamps
end
add_index :support_messages, [:support_chat_id, :created_at]
```

React Hook 去重核心:

```typescript
ws.onmessage = (event) => {
  const data = JSON.parse(event.data)
  if (data.type === 'new_message' && !seenIds.current.has(data.message.id)) {
    seenIds.current.add(data.message.id)
    setChat(prev => prev ? { ...prev, messages: [...prev.messages, data.message] } : prev)
  }
}
const sendMessage = (content: string) =>
  wsRef.current?.send(JSON.stringify({ action: 'send_message', content }))
```

Widget 未读角标:`chat.messages.filter(m => m.sender_type==='admin' && !m.read_at).length`,>9 显示 `9+`。

其他栈速记:Next.js(App Router)用 `getServerSession` 鉴权、Prisma `findUnique({where:{userId}})` 取或建会话,serverless 走 Pusher `trigger`/`subscribe`;Laravel 在 `booted()` 的 `created` 钩子里 `broadcast(new NewSupportMessage($message))->toOthers()` 并 `delay(now()->addMinutes(5))`;Vue 用 `useSupportChat` composable,逻辑同 React。

实时技术选型:Rails→ActionCable,Node→Socket.IO,任意栈+serverless→Pusher/Ably/Supabase Realtime;WebSocket 不可用时降级为每 5 秒 `?since=lastMessageTime` 轮询。

数据库:PostgreSQL(推荐,UUID + `timestamptz`)、MySQL(`CHAR(36)`/`BINARY(16)` + `utf8mb4` 存 emoji)、SQLite(原型,UUID 存 TEXT、时间存 ISO8601)、MongoDB(消息量有界可内嵌)。邮件服务:Postmark/SendGrid/AWS SES/Resend。

## 注意事项

- 必做去重:自己发的消息会经广播回声重复出现,务必用 seen-id 集合拦截。
- 读状态有竞态:更新 `read_at` 走数据库事务。
- WebSocket 必须鉴权:校验当前用户能否访问该具体会话。
- 优雅处理重连,避免连接状态显示陈旧。
- 别漏复合索引 `(chat_id, created_at)`,否则消息排序慢。
- 邮件一律走后台 Job,禁止同步发送;发送前再次校验是否仍未读/未归档。

验收清单:用户发消息→管理员实时可见→回复用户即时收到;未读角标计数正确、打开即清零;连接指示反映真实状态;归档/恢复正常、用户来信自动复活;5 分钟后仅在未读时发邮件、已读则不发;消息时序正确且无重复。

本技能仅在任务明确落在上述范围内时使用;产出不替代针对具体环境的验证、测试与专家评审。

## 互见

- 实时通信底座选型(ActionCable / Socket.IO / Pusher / Ably / SSE)。
- 后台任务队列与延时 Job(邮件通知调度)。
- 用户鉴权与角色(区分 user / admin)。

---

采编自 sickn33/antigravity-awesome-skills(MIT)。

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…