云开发私人实时聊天室
发布于 2 年前 作者 xiulan03 3551 次浏览 来自 分享

云开发私人实时聊天室

说明

在最开始开发小程序时,本人和团队成员实现小程序的聊天室时遇到一些困难,查阅了一些资料,有些讲得太泛,有些讲的太难,在一个阶段克服了这个困难后,收获了很多,对整个流程也熟悉了很多,在这里记录自己的一个思路,希望也能对开发新手有帮助。

项目基本配置

1.项目创建及云开发配置:

官方文档:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/quick-start/miniprogram.html

PS:注意云函数目录是否为此样式:

若是普通目录样式记得在project.config.json中配置加入:

2.添加包colorui,用于样式使用,并在app.wxss中导入改包

3.在pages下新建文件夹index和新建page:index

聊天室静态页面

最终呈现的效果:

自己:

对方:

1. wxml

整体结构:

整一个页面说白了就是由一个scroll-view和一个回复框组成,scroll-view中由消息数组构成,消息的内容可以自己定义(时间,头像,消息内容等等)

具体源码:

<!-- scroll-view来实现页面拖动 -->
<scroll-view id='page' scroll-into-view="{{toView}}" upper-threshold="100"
  scroll-y="true" enable-back-to-top="true" class="message-list">
  <!-- 每一条消息 -->
  <view class="cu-chat" wx:for="{{3}}" wx:key="index" id="row_{{index}}">
    <!-- 自己发出的消息 -->
    <block wx:if="{{false}}">
      <block wx:if="{{true}}">
        <view class="datetime" style="width:100%">2021-11-16 18:10</view>
      </block>
      <view class="cu-item self"
        style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
        <view class="main">
          <view class="content bg-green shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx">
            <text style="font-size:33rpx">这是一条消息</text>
          </view>
        </view>
        <view class="cu-avatar radius center"
          style="background-image: url({{useravatar}}); width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx"
          bindtap="go_myinfo"></view>
      </view>
    </block>
     <!-- 对方发出的消息 -->
     <block wx:else>
      <block wx:if="{{true}}">
        <view class="datetime" style="width:100%">2021-11-16 19:10</view>
      </block>
      <view class="cu-item"
        style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
          <view class="cu-avatar radius center"
            style="background-image: url({{match_avatar}}); width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
          </view>
        <view class="main">
          <view class="content bg-white shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx">
            <text style="font-size:33rpx">这是对面的一条消息</text>
          </view> 
        </view>
      </view>
    </block>
  </view>
</scroll-view>

<!-- 回复框 -->
<view class="reply cu-bar">
  <!-- 输入框 -->
  <view class="opration-area">
    <input type="text" bindinput="getContent" value="{{textInputValue}}" maxlength="300" cursor-spacing="10"
      style="width: 544rpx; height: 64rpx; display: block; box-sizing: border-box;"></input>
  </view>
  <!-- 发送按钮 -->
  <button class="cu-btn bg-green shadow" bindtap='sendMsg'
    style="width: 150rpx; height: 64rpx; display: flex; box-sizing: border-box; left: -22rpx; top: 0rpx; position: relative">发送</button>
</view>

2. wxss

一些样式的配置,具体就不详细叙述了,见源码:

/*消息窗口*/
.message-list {
    margin-bottom: 54px;
}
​
/*文本输入或语音录入*/
.reply .opration-area {
    flex: 1;
    padding: 8px;
}
​
/*回复文本框*/
.reply input {
    background: rgb(252, 252, 252);
    height: 36px;
    border: 1px solid rgb(221, 221, 221);
    border-radius: 6px;
    padding-left: 3px;
}
​
/*回复框*/
.reply {
    display: flex;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    position: fixed;
    bottom: 0;
    width: 100%;
    height: 108rpx;
    border-top: 1px solid rgb(215, 215, 215);
    background: rgb(245, 245, 245);
}
​
/*日期*/
.datetime {
    font-size: 10px;
    padding: 10px 0;
    color: #999;
    text-align: center;
}

到此,静态的页面就已经做好啦,现在主要的难题也是数据部分,下面将先讲述数据库chatroom的设计及解释,最后进行js的代码编写。

数据库创建及设计

1.数据库表创建:

在编辑器打开云开发控制台,点击数据库,再点击集合名称右边加号,创建一个集合名称为chatroom的表。

2.chatroom设计

具体页面如图:

其中,

_id为记录创建时自动创建的标识属性,即主键

_openid和match_openid代表了自身和对方

records为一个对象数组,每个对象的属性分别是:

msgText:消息属性(此案例中只有text属性,即文本,可自扩展为图片、音频等)

openid:发送人的标识

sendTime:消息创建时间

sendTimeTS:消息创建时的时间戳(用于做时间比较,判断时间显示)

showTime:消息是否显示时间

textContent:具体文本内容

其中,

records:array类型,

records中的记录:object类型

records中的sendTimeTS:number类型

records中的showTime:boolean类型

其余全为string类型

PS:

1、openid和match_openid可标识一个聊天室,是唯一不变的;

2、用户本身的openid是有可能在记录中的match_openid位置上的,谁发起了这个聊天室,openid这个位置就是那个发起用户的openid,所以在开发中,想要获取自己和所有其他人的聊天室,要查每条记录中的openid或者match_openid与自身openid是否匹配。

3.权限设置

因为该表中的记录,非记录创建者也可以进行读写,这里的权限记得设置,不然会出问题:

具体功能实现(JS写法)

1.先配置Page.data:6个属性,如有需要可自行扩展

chats存储数据库表中的records的所有信息;

textInputValue是输入框内容。

2.绑定数据库表onChange函数:

这里的onChange输出e是这样的:

type=init,获取了数据库表中该记录的所有内容,在这里将js中的chats进行赋值即可;

另外,当该记录内容变化时,type是update类型

3.wxml修改,wx-for将chats显示,以及一些判断和内容显示的设置:

到此,显示效果就有啦

接下来,就是信息的添加了,下面将显示如何添加新信息到数据库

4.发送信息

先获取输入框内容:

发送函数:

增加一条信息,就是在records数组中加一条记录,所以在函数内部要对新纪录的属性进行一些赋值和判断等。

对showTime的处理:

消息空白处理:

对消息内的所有属性进行一个打包处理:

存储记录,并滑动页面:

最后,清空消息框内容

发送一条消息,最终效果如图:

js源码

const app = getApp()
const db = wx.cloud.database()
const _ = db.command
const chatroomCollection = db.collection("chatroom")
var util = require('../../utils/util.js');
​
Page({
​
  data: {
    //这里的openid和match_openid应该是在上一级页面传进来的属性,这里由于只有聊天室所以暂时设置为一些固定值,用于测试
    openid:'',
    match_openid:'',
    //这里的avatar是头像,具体传参方式自己设定,这里暂时设置为固定值,用于测试
    useravatar:'',                       
    match_avatar:'',
    chats:[],
    textInputValue:''
  },
​
  onReady() {
    var that = this
    //查询openid和match_openid所标识的唯一聊天室
    chatroomCollection.where({
        _openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)),
        match_openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid))
      })
      //绑定onChange,直观而言即表中该记录发生变动时,调用该函数
      .watch({
        onChange: this.onChange.bind(this),
        onError(err) {
          console.log(err)
        }
      })
  },
​
​
  //数据库表onchange绑定函数
  onChange(e) {
    let that = this
    //type="init"的情况:初始化聊天窗口信息
    if (e.type == "init") {
      that.initchats(e.docs[0].records)
    }
    //type="update"的情况:records中增加了一条记录
    else {
      //在chats数组中增加该新消息
      let i = that.data.chats.length
      const new_chats = [...that.data.chats]
      if (e.docs.length)
        new_chats.push(e.docs[0].records[i])
      this.setData({
        chats: new_chats
      })
    }
  },
​
  initchats(records) {
    this.setData({
      chats: records
    })
    //跳转到页面底部
    this.goBottom()
  },
​
  //获取输入文本
  getContent(e) {
    this.data.textInputValue = e.detail.value
  },
​
  sendMsg(){
    let that = this
    //show代表了数据库表中的showTime属性,是否显示消息时间
    var show = false
    //无记录时,true
    if (this.data.chats.length == 0)
      show = true
    //判断上下两条消息的时间差决定是否显示时间,这里设置了2分钟:120000毫秒,可自行修改
    else {
      if (Date.now() - this.data.chats[this.data.chats.length - 1].sendTimeTS > 120000)
        show = true
    }
​
    const _ = db.command
    //消息空白处理
    if (!that.data.textInputValue) {
      wx.showToast({
        title: '不能发送空白信息',
        icon: 'none',
      })
      return
    }
​
    //消息内容赋值
    const doc = {
      openid: that.data.openid,
      msgText: "text",
      textContent: that.data.textInputValue,
      sendTime: util.formatTime(new Date()),
      sendTimeTS: Date.now(),
      showTime: show,
    }
​
    //添加数据库表中该记录的records数组,并跳转页面到底部
    chatroomCollection.where({
        _openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid)),
        match_openid: _.or(_.eq(that.data.openid), _.eq(that.data.match_openid))
      })
      .update({
        data: {
          records: _.push(doc)
        }
      })
      .then(res => {
        that.goBottom()
      })
​
    //消息设空
    that.setData({
      textInputValue: ""
    })
  },
​
  goBottom() {
    wx.createSelectorQuery().select('#page').boundingClientRect(function (rect) {
      if (rect) {
        // 使页面滚动到底部
        wx.pageScrollTo({
          scrollTop: rect.height + 4
        })
      }
    }).exec()
  },
})

其中,util.js内容如下:

const formatTime = date => {
  const year = date.getFullYear()
  const month = date.getMonth() + 1
  const day = date.getDate()
  const hour = date.getHours()
  const minute = date.getMinutes()
  const second = date.getSeconds()
​
  return `${[year, month, day].map(formatNumber).join('/')} ${[hour, minute, second].map(formatNumber).join(':')}`
}
​
const formatNumber = n => {
  n = n.toString()
  return n[1] ? n : `0${n}`
}
​
module.exports = {
  formatTime
}
​

一个简单的demo就完成了,大家有什么问题欢迎随时q我。

-----------完结撒花----------

2 回复

过程很详细,只不过我想提个问题,就是这个聊天室demo是实时聊天的吗?

看过程感觉实时聊天每次发消息后,对方都要退出实时聊天页面,获取到文章中说的“要查每条记录中的openid或者match_openid与自身openid是否匹配”,再重新点击对应的对话框进到实时聊天界面,而不能一直呆在我跟他人的对话框页面进行实时对话?

很不错喔,点个大大的赞

回到顶部