云开发私人实时聊天室
说明
在最开始开发小程序时,本人和团队成员实现小程序的聊天室时遇到一些困难,查阅了一些资料,有些讲得太泛,有些讲的太难,在一个阶段克服了这个困难后,收获了很多,对整个流程也熟悉了很多,在这里记录自己的一个思路,希望也能对开发新手有帮助。
项目基本配置
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我。
-----------完结撒花----------
过程很详细,只不过我想提个问题,就是这个聊天室demo是实时聊天的吗?
看过程感觉实时聊天每次发消息后,对方都要退出实时聊天页面,获取到文章中说的“要查每条记录中的openid或者match_openid与自身openid是否匹配”,再重新点击对应的对话框进到实时聊天界面,而不能一直呆在我跟他人的对话框页面进行实时对话?
很不错喔,点个大大的赞
请问 小程序切换到后台。请问此时 别人发的消息 我会收到新消息提醒吗(声音提醒)?目前看小程序切换到后台,没法通过声音提醒用户有新消息。
即使采用给公众号发 模版消息,公众号也只有红点提醒,没有声音提醒
重新研究过后发现了一个问题,我想请教您一下:请问e.type是在何处更新的呢?什么时机修改为init,什么时机修改为update呢?谢谢您🌹
init:在刚进这个页面获取到整个聊天室的信息时,也就是第一次获取聊天的信息,type==init
update:在这条记录变化时,比如records中新增了一条记录,程序会主动调用onChange函数,从而更新我们的页面的records数据,从而保持数据库的聊天数据和当前页面的聊天数据一致
谢谢您的解答。还想请教您一下,如果我想扩展发送消息的类型,应该修改哪些部分呢?
比方说,除了发送文字,我还想支持发送图片,那么可以直接基于这个框架进行修改吗?
谢谢您,打扰您了🌹
存储方面,如果type为文字你就保存文字的信息,type为图片时你就需要先存储图片到指定位置(比如各平台的对象存储,能提供文件的存储,并生成url访问地址)然后将这个url存到对应的聊天数据中就可以了
具体的难点主要是前端的样式问题,通过判断type的不同显示不同的view就可以了,逻辑上这套框架是可以支持的
您好,我在进入聊天页面过程中,触发以下问题,请问是什么原因呢?如何处理呢?谢谢您,打扰您了🌹
<!-- 对方发出的消息 -->
<block wx:else>
<block wx:if="{{item.showTime}}">
<view class="datetime" style="width:100%">{{item.sendTime}}</view>
</block>
<view class="cu-item"
style="width: 750rpx; height: 120rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx">
<image class="cu-avatar radius center"
style="width: 71rpx; height: 71rpx; display: flex; box-sizing: border-box; left: 0rpx; top: 0rpx" mode="aspectFill" src="{{match_avatar}}"></image>
<view class="main">
<view class="content bg-white shadow" style="position: relative; left: 0rpx; top: 22rpx;border-radius: 10rpx">
<text style="font-size:33rpx">{{item.textContent}}</text>
</view>
</view>
</view>
</block>
</view>
</scroll-view>
请问下,个人开发者开发这种聊天室小程序提交审核能通过吗
你好,在实践的过程中发现第一次初始化即"init"时候,得到的e.docs[]其实是空的,是不是应该先创建这条记录呢
你好,这个参与聊天室的人员,可以是直接微信的账号吗?还是需要授权/注册然后生成自己系统的账号?
我是想,微信账号可以直接参与聊天,不用再注册新的小程序聊天系统的账号。你这个实现是支持的吗?
你好 我想问一下 你这做了下拉加载历史消息吗 类似微信的