WebSocket 管理后台
创建新项目
项目列表
连接配置
WebSocket:
SSE:
Polling:
WebRTC 音视频通话测试
本地视频
通话控制
远端视频
通话日志
邮件发送测试
WebSocket Server API 技术文档
概述
这是一个部署在 Cloudflare Workers 上的多项目隔离 WebSocket 服务器,支持 WebSocket、SSE(Server-Sent Events)和 Long Polling 三种传输方式。
多协议支持
WebSocket / SSE / Long Polling
项目隔离
独立 Durable Object 实例
离线消息
3天自动过期存储
WebRTC
视频/语音通话支持
管理 API
所有管理 API 需要在请求头中携带 Admin Token:
Authorization: Bearer <admin-token>
| 方法 | 路径 | 描述 | 请求体 |
|---|---|---|---|
| POST | /project | 创建项目 | {"key": "xxx", "name": "xxx"} |
| GET | /project | 列出项目 | - |
| DELETE | /project?key=xxx | 删除项目 | - |
| POST | /token | 生成令牌 | {"projectKey": "xxx"} |
| DELETE | /token?projectKey=xxx&token=xxx | 删除令牌 | - |
| PATCH | /token | 修改令牌到期时间 | {"projectKey": "xxx", "token": "xxx", "expiresAt": xxx} |
邮件发送 API
使用 POST 方式发送邮件,通过 project 和 token 参数进行身份验证。
邮件发送通过 MailChannels 服务实现,需要在域名 DNS 中配置 SPF 记录。
| 方法 | 路径 | 描述 |
|---|---|---|
| POST | /mail?project=xxx&token=xxx | 发送邮件 |
请求参数
to (必填): 收件人邮箱,支持字符串或数组
subject (必填): 邮件主题
text: 纯文本内容
html: HTML 内容
from: 发件人邮箱(默认 noreply@域名)
fromName: 发件人名称(默认 WebSocket Server)
示例请求
POST /mail?project=myproject&token=xxx
Content-Type: application/json
{
"to": "user@example.com",
"subject": "测试邮件",
"text": "这是一封测试邮件",
"html": "<p>这是一封 <strong>测试邮件</strong></p>",
"fromName": "通知服务"
}
响应示例
{
"status": "success",
"msg": "Email sent successfully",
"rateLimit": {
"allowed": true,
"count": 1,
"max": 10
}
}
频率限制
每个令牌每分钟最多发送 10 封邮件。超过限制将返回 429 状态码。
DNS 配置要求
为了使邮件能正常发送,需要在域名的 DNS 记录中添加以下配置:
1. 域名锁记录(必需)
证明你拥有该域名,MailChannels 才能代发邮件:
TXT _mailchannels v=mc1 cfid=你的 Cloudflare Account ID
2. SPF 记录(必需)
允许 MailChannels 代表你的域名发送邮件:
TXT @ v=spf1 include:spf.mailchannels.net ~all
3. DKIM 记录(可选但推荐)
增加邮件真实性验证,提高送达率:
CNAME _dmarc.yourdomain.com → dmarc.mailchannels.net
配置完成后,需要等待 DNS 记录生效(通常 5-15 分钟),邮件才能正常发送。
连接方式
所有连接方式都需要携带 project、token 和 mode 参数。
wss://scws.ccwu.cc/ws?project=myproject&token=xxx&mode=long
全双工通信,实时性最好,推荐使用
mode 参数:
short: 短连接模式,无服务端心跳(默认)long: 长连接模式,服务端每 15 秒发送一次 ping 消息
示例代码
WebSocket 连接
const ws = new WebSocket('wss://scws.ccwu.cc/ws?project=myproject&token=xxx&mode=long');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'bind', uid: 'user_1' }));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'connected': console.log('Connected:', msg.connId); break;
case 'bind_success': console.log('Bound:', msg.uid); break;
case 'direct': console.log('Received:', msg); break;
case 'ping': ws.send(JSON.stringify({ type: 'pong' })); break;
}
};
// 发送消息
ws.send(JSON.stringify({ type: 'send', to: 'user_2', msg: 'Hello' }));
ws.send(JSON.stringify({ type: 'broadcast', msg: 'Hi everyone' }));
SSE 连接
const eventSource = new EventSource('https://scws.ccwu.cc/sse?project=myproject&token=xxx&mode=long');
eventSource.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log('Received:', msg);
};
// 发送消息(通过 HTTP POST)
fetch('https://scws.ccwu.cc/sse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'send', to: 'user_2', msg: 'Hello' })
});
WebRTC 视频通话
const ws = new WebSocket('wss://scws.ccwu.cc/ws?project=myproject&token=xxx&mode=long');
let pc = null;
let localStream = null;
// 连接并绑定 UID
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'bind', uid: 'user_1' }));
ws.send(JSON.stringify({ type: 'room_join', roomId: 'room_123' }));
};
ws.onmessage = async (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'webrtc_offer') {
// 收到通话请求
pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
// 添加本地流
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
// 处理远端流
pc.ontrack = (e) => {
document.getElementById('remoteVideo').srcObject = e.streams[0];
};
// 发送 ICE Candidate
pc.onicecandidate = (e) => {
if (e.candidate) {
ws.send(JSON.stringify({
type: 'webrtc_ice',
roomId: 'room_123',
to: msg.from,
candidate: e.candidate.toJSON()
}));
}
};
// 设置远端描述并发送应答
await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
ws.send(JSON.stringify({
type: 'webrtc_answer',
roomId: 'room_123',
to: msg.from,
sdp: answer
}));
}
if (msg.type === 'webrtc_answer') {
await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp));
}
if (msg.type === 'webrtc_ice') {
await pc.addIceCandidate(new RTCIceCandidate(msg.candidate));
}
};
// 发起通话
async function startCall(targetUid) {
localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
document.getElementById('localVideo').srcObject = localStream;
pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
localStream.getTracks().forEach(track => pc.addTrack(track, localStream));
pc.ontrack = (e) => {
document.getElementById('remoteVideo').srcObject = e.streams[0];
};
pc.onicecandidate = (e) => {
if (e.candidate) {
ws.send(JSON.stringify({
type: 'webrtc_ice',
roomId: 'room_123',
to: targetUid,
candidate: e.candidate.toJSON()
}));
}
};
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({
type: 'webrtc_offer',
roomId: 'room_123',
to: targetUid,
sdp: offer
}));
}
// 开始通话
startCall('user_2');
发送邮件
// 发送邮件 (POST 请求)
async function sendEmail(projectKey, token) {
const response = await fetch(`https://scws.ccwu.cc/mail?project=${projectKey}&token=${token}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
to: 'user@example.com',
subject: '通知邮件',
text: '这是一封重要的通知邮件',
html: '<p>这是一封 <strong>重要</strong> 的通知邮件</p>',
fromName: '系统通知'
})
});
const result = await response.json();
if (response.ok) {
console.log('邮件发送成功:', result);
} else {
console.error('发送失败:', result);
}
}
// 使用
sendEmail('myproject', 'xxx');
消息协议
bind
绑定 UID
{"type": "bind", "uid": "user_1"}
broadcast
广播消息
{"type": "broadcast", "msg": "Hello"}
send
一对一消息
{"type": "send", "to": "user_2", "msg": "Hi"}
multicast
群发消息
{"type": "multicast", "to": ["u1","u2"], "msg": "Hi"}
list
获取在线列表
{"type": "list"}
online
查询在线状态
{"type": "online", "uid": "user_1"}
服务器返回消息
| 类型 | 说明 | 示例 |
|---|---|---|
| connected | 连接成功 | {"type": "connected", "connId": "xxx"} |
| bind_success | 绑定成功 | {"type": "bind_success", "uid": "user_1"} |
| direct | 收到一对一消息 | {"type": "direct", "from": "u1", "to": "u2", "msg": "..."} |
| broadcast | 收到广播消息 | {"type": "broadcast", "msg": "...", "timestamp": xxx} |
| send_success | 发送成功 | {"type": "send_success", "to": "user_2"} |
| online_result | 查询在线结果 | {"type": "online_result", "uid": "u1", "online": true} |
| list | 在线用户列表 | {"type": "list", "uids": ["u1", "u2"]} |
| online | 用户上线通知 | {"type": "online", "uid": "u1", "timestamp": xxx} |
| offline | 用户下线通知 | {"type": "offline", "uid": "u1", "timestamp": xxx} |
| ping | 心跳(长连接) | {"type": "ping"} |
| error | 错误消息 | {"type": "error", "msg": "..."} |
离线消息
- 一对一消息和群发消息支持离线存储
- 当目标用户不在线时,消息会自动存入 KV
- 消息存储时间:3天(自动过期)
- 用户上线后自动接收离线消息
// 发送离线消息时返回
{"type": "send_success", "to": "user_2", "offline": true}
// 群发时返回在线和离线用户
{"type": "send_success", "to": ["user_2"], "offline": ["user_3"]}
WebRTC 支持
服务器可作为 WebRTC 信令服务器,用于交换 SDP 和 ICE Candidates。
通话模式
视频通话
同时传输视频和音频
需要:摄像头 + 麦克风
语音通话
仅传输音频
需要:麦克风
房间管理
room_create
创建房间
{"type": "room_create", "roomId": "r1"}
room_join
加入房间
{"type": "room_join", "roomId": "r1"}
room_leave
离开房间
{"type": "room_leave", "roomId": "r1"}
WebRTC 信令
// 发送 Offer
{"type": "webrtc_offer", "roomId": "r1", "to": "user2", "sdp": {...}}
// 发送 Answer
{"type": "webrtc_answer", "roomId": "r1", "to": "user1", "sdp": {...}}
// 发送 ICE Candidate
{"type": "webrtc_ice", "roomId": "r1", "to": "user2", "candidate": {...}}
工作流程
- 客户端 A 和 B 分别连接服务器并绑定 UID
- A 创建房间,B 加入同一房间
- A 发起通话请求(webrtc_offer)
- B 收到请求后发送应答(webrtc_answer)
- 双方交换 ICE Candidates(webrtc_ice)
- P2P 连接建立,开始音视频传输
STUN 服务器
stun:stun.l.google.com:19302
技术栈
Cloudflare Workers
运行环境
Durable Objects
状态管理
Workers KV
配置存储
WebSocket/SSE/Polling
协议支持