引言
业余时间,闲来无事,想要利用云开发搭建一个即时通信的小工具?本文将带你一步步实现这个目标。一顿饭的时间,你的即时通信小工具就可以立马上线啦!
在本文中,我们将介绍如何利用腾讯云云开发快速搭建一个即时通信应用,解决长连接问题、实时推送功能、数据存储问题以及音视频问题。
准备工作
- 首先,你需要在腾讯云官网开通 云开发 CloudBase 服务。届时,你将拥有云函数、数据库、云存储、登录、外网访问云函数等诸多能力和功能。这些将作为我们的后端服务基础。
- 接下来,创建一个 Ant Design Pro 项目,用于开发前端界面。
实施过程
虽然 GitHub 上有很多优秀的库可以实现即时通信,但大多需要一定的理解成本和试错成本。本文遵循"时间就是金钱"的原则,希望能用最短的时间,搭建出一个基本可用的应用服务。正所谓"君子善假于物也"
云服务
创建数据库表
- 进入云开发控制台,在数据库页面新增一个「todo」的集合,用于存储我们的通信信息。
- 在「todo」集合中,我们会将一些基本字段,如:
_id
(主键,这个会自动生成)、content
(消息内容)、timestamp
(时间戳)、sender
(发送者)等存储进去。这些字段将帮助我们存储和查询聊天记录。
Ant Design Pro 项目
使用 Ant Design Pro 搭建好基本的前端框架后,我们需要创建一个聊天界面。在聊天界面中,我们可以添加一个输入框(用于输入消息)、一个消息列表(用于展示聊天记录)以及一个发送按钮(用于发送消息)。
接下来,我们需要实现消息的发送和接收功能。为了实现这个功能,我们可以在前端项目中引入腾讯云云开发的 @cloudbase/js-sdk
,并创建一个云开发实例。通过这个实例,我们可以调用云开发的接口,实现消息的发送和接收。
ts
import cloudbase from '@cloudbase/js-sdk';
// 获取 云 sdk 实例
export const app = (): cloudbase.app.App =>
cloudbase.init({
env: {your-id}, // 您的环境id
});
// 获取鉴权实例
export const getAuth = (): cloudbase.auth.App => {
// 会话鉴权,失效策略
const auth = app().auth({ persistence: 'session' });
return auth;
};
// 获取数据库实例
export const getDB = (): cloudbase.database.App => {
return app().database();
};
实现长连接
云开发数据库实例的 watch
接口,监听「todo」集合中的数据变化。当数据发生变化时,我们可以实时更新消息列表,从而实现消息的实时推送功能。前端 @cloudbase/js-sdk
能够直接操作数据库,安全又稳定,实现消息的发送
ts
const db = getDB();
// 接收消息
useEffect(() => {
const _ = db.command;
const watcher = db
.collection('todo')
.watch({
onChange: (snapshot: any) => {
// 当数据库有变化是,实时拉取的最新的数据
setDataSource(snapshot.docs);
},
onError: (err: any) => {
console.error('the watch closed because of error', err);
},
});
return () => {
watcher.close();
};
}, []);
ts
const db = getDB();
/** 发送消息
* 定义三种消息类型:image text aduio
*/
const onSend = (type, content) => {
db.collection('todo').add({
// 消息内容
content,
// 消息类型
type
});
}
当数据库中的数据发生变化时,监听接口会自动触发,从而实现数据的实时同步。从浏览器控制台打印的信息来看,@cloudbase/js-sdk
中实现的实时推送,也是基于 WebSocket
来实现
音频处理
音频的收集是借助 navigator.mediaDevices
实现,然后将收集到的音频数据存储到云上,并将云上的音频地址,保存到即时通信的数据表中(todo 集合)
可以看到 navigator.mediaDevices 的兼容性很不错,接下来是具体实现
tsx
const AudioRecorder = ({ handleAudio = noop }) => {
const [audioBlob, setAudioBlob] = useState(null);
let mediaRecorder = useRef(null);
let chunks = [];
const startRecording = () => {
// 创建音频
navigator.mediaDevices
.getUserMedia({ audio: true })
.then((stream) => {
mediaRecorder.current = new MediaRecorder(stream);
mediaRecorder.current.addEventListener('dataavailable', (event) => {
chunks.push(event.data);
});
mediaRecorder.current.addEventListener('stop', () => {
const blob = new Blob(chunks, { type: 'audio/webm' });
setAudioBlob(blob);
chunks = [];
});
mediaRecorder.current.start();
})
.catch((error) => {
console.error('无法访问麦克风', error);
});
};
const stopRecording = () => {
if (mediaRecorder.current?.state === 'recording') {
mediaRecorder.current.stop();
// 上传录音
uploadAudio();
}
};
// 将音频信息上传到云存储中,获取到音频地址
const uploadAudio = async () => {
if (audioBlob) {
try {
const res = await app().uploadFile({
// 云存储的路径
cloudPath: `im/${new Date().getTime()}.webm`,
// 需要上传的文件,File 类型
filePath: audioBlob,
});
// 处理音频地址,将音频地址保存到 todo 集合中
onSend('audio', res?.download_url);
} catch (error) { }
}
};
return (
<Button onClick={recording ? stopRecording : startRecording} />
);
}
渲染音频消息很好实现,直接使用 <audio />
标签
ts
{type === 'audio' && <audio className="audio" controls src={content} />}
处理图片和表情
本地图片处理使用 antd 组件库的 Upload 组件
tsx
const UploadButton: React.FC = ({ handleUpload = noop }) => {
const props = {
showUploadList: false,
customRequest: async (file) => {
const { name } = file.file;
const res = await app().uploadFile({
// 云存储的路径
cloudPath: `im/${name}`,
// 需要上传的文件,File 类型
filePath: file.file,
});
file.onSuccess(res, file);
},
onChange(info) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
const { download_url } = info.file.response;
handleUpload(download_url);
} else if (info.file.status === 'error') {
message.error(`${info.file.name} file upload failed.`);
}
},
};
return (
<Upload {...props}>
<Button />
</Upload>
);
};
重点介绍一下表情的处理,表情又分为 emoji 和 gif 图
- emoji (文本类型)
ts
// 写个组件遍历这组数据就好,然后调用 onSend('text', emoji) 方法发送,注意这里是发送 text 类型的消息
const emojis = [
'😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊', '😋', '😎', '😍', '😘', '😗', '😙', '😚', '🙂', '🤗', '🤔', '😐', '😑', '😶', '🙄', '😏', '😣', '😥', '😮', '🤐', '😯', '😪', '😫', '😴', '😌', '🤓', '😛', '😜', '😝', '🤤', '😒', '😓', '😔', '😕', '🙃', '🤑', '😲', '🙁', '😖', '😞', '😟', '😤', '😢', '😭', '😦', '😧', '😨', '😩', '😬', '😰', '😱', '😳', '😵', '😡', '😠', '🤬', '😷', '🤒', '🤕', '🤢', '🤮', '🤧', '😇', '🤠', '🤥', '🤫', '🤭', '🧐', '🤯', '🤪', '🥳', '🥴', '🥺', '🥰', '🥵', '🥶', '🥱', '🤎', '🤍'
]
- gif 图
Giphy 是一个专门提供 GIF 动图的网站,用户可以在上面搜索、制作和分享 GIF 动图
关于gif图的实现思路,我们将借助 Giphy 平台。Giphy 提供了相关的 API,可以实现通过关键词来搜索出符合条件的 gif 图列表,进而实现 gif 图的渲染和选择。通过这种方式,我们可以丰富即时通信应用的功能,让用户在聊天过程中可以方便地发送和查看有趣的 GIF 动图。
为了实现这个功能,我们可以在前端项目中通过网络请求,并调用 Giphy 提供的搜索接口。当用户在聊天界面输入关键词并点击搜索按钮时,我们可以调用 Giphy 的搜索接口,获取相关的 gif 图列表。
ts
// 获取gif
export const getGif = async (page: number = 0, perPage: number = 40, query = '') => {
const repos = await request(`https://api.giphy.com/v1/gifs/search?api_key=${apiKey}&q=${query}&limit=${perPage}&offset=${page}`);
return repos;
};
const onSearch = async (value) => {
const { data = [] } = await getGif(0, 40, value)
const gifs = data.map(item => item.images?.downsized?.url || '')
// 更新 gif 图列表
setList(gifs)
}
// 根据用户输入,调用 onSearch 方法
<Search placeholder="请输入" allowClear onSearch={onSearch} onPressEnter={onSearch} />
渲染图片消息使用 antd 组件库的 Image 组件
ts
{type === 'image' && <Image width={200} src={content} />}
处理文本
用户输入完,点击发送按钮,调用 onSend('text', value) 上传就好
ts
<TextArea
value={value}
autoSize={{ minRows: 1, maxRows: 6 }}
onChange={onChange}
onPressEnter={onPressEnter}
/>
处理浏览器通知
浏览器通知的功能需要用户手动开启,对于 Mac 用户来说,除了开启浏览器网页通知,还需要在
电脑系统设置
中,允许接受
浏览器的消息通知
当有新的消息过来时,我们希望浏览器的网页能发出通知提示,触达用户
ts
// 显示通知的函数
function showNotification() {
const title = 'Hello!'; // 自定义文案
const options = {
body: '提醒您喝水~.',
icon: 'https://xxx', // 可选,通知图标的URL
};
const notification = new Notification(title, options);
// 可选,处理通知点击事件
notification.onclick = () => {
notification.close()
};
}
// 集成通知功能
export const notification = () => {
// 检查浏览器是否支持通知
if ('Notification' in window) {
// 检查用户是否已授权通知
if (Notification.permission === 'granted') {
showNotification();
} else if (Notification.permission !== 'denied') {
// 请求用户授权
Notification.requestPermission().then((permission) => {
if (permission === 'granted') {
showNotification();
}
});
}
} else {
console.log('Your browser does not support notifications.');
}
}
部署前端页面
可以将打包之后的前端资源,放到云开发的 静态网站托管
中,支持自定义域名
效果
我们看下 云存储
和 数据库
的情况,我们可以通过控制台,对上传的数据进行 增删改查
- 云存储
可以看到,上传的图片和音频资源,可以正常存储
- 数据库
对于图片和音频资源,数据库存储的是网络地址,并且每条数据还 自动生成了 _id
总结
通过以上步骤,我们已经成功搭建了一个基本的即时通信应用。虽然这个应用还有很多可以优化的地方,但它已经具备了基本的通信功能。此外,它还 免运维
。希望本文能帮助你快速搭建自己的即时通信小工具