轻松搭建免运维即时通信应用:一顿饭的时间实现聊天小工具

引言

业余时间,闲来无事,想要利用云开发搭建一个即时通信的小工具?本文将带你一步步实现这个目标。一顿饭的时间,你的即时通信小工具就可以立马上线啦!

在本文中,我们将介绍如何利用腾讯云云开发快速搭建一个即时通信应用,解决长连接问题、实时推送功能、数据存储问题以及音视频问题。

准备工作

  • 首先,你需要在腾讯云官网开通 云开发 CloudBase 服务。届时,你将拥有云函数、数据库、云存储、登录、外网访问云函数等诸多能力和功能。这些将作为我们的后端服务基础。
  • 接下来,创建一个 Ant Design Pro 项目,用于开发前端界面。

实施过程

虽然 GitHub 上有很多优秀的库可以实现即时通信,但大多需要一定的理解成本和试错成本。本文遵循"时间就是金钱"的原则,希望能用最短的时间,搭建出一个基本可用的应用服务。正所谓"君子善假于物也"

云服务

创建数据库表

  1. 进入云开发控制台,在数据库页面新增一个「todo」的集合,用于存储我们的通信信息。
  2. 在「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

总结

通过以上步骤,我们已经成功搭建了一个基本的即时通信应用。虽然这个应用还有很多可以优化的地方,但它已经具备了基本的通信功能。此外,它还 免运维。希望本文能帮助你快速搭建自己的即时通信小工具

相关推荐
也无晴也无风雨30 分钟前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤5 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js
别忘了微笑_cuicui6 小时前
elementUI中2个日期组件实现开始时间、结束时间(禁用日期面板、控制开始时间不能超过结束时间的时分秒)实现方案
前端·javascript·elementui