聊天服务器端,使用上一个文章里面提到的websocket服务器端,
通讯地址格式ws://ip:7878:/c/sec/userid
大致思路,首先去app的服务器登录,返回用户信息,里面含有用户编号。通过获取配置的方式,获取聊天url,当然,在app里面写死也可以,我是做成了一个后台配置项。
app拿到链接地址,加上当前用户的id,使用uni.connectSocket进行链接,使用uni.onSocketMessage接收用户消息。接收到消息存到本地sqllite。
进入聊天界面,首先去查询本地sqllite,显示历史消息,然后通过回调方式,监听实时消息。并显示。
使用store存储实时消息,ws接收到消息以后,推送到store,页面对store里面的变量进行watch,实时更新页面。
效果图
关键代码1:sqllite.js,完成数据库初始化,数据操作,注意uniapp使用sqllite需要勾选对应插件,必须真机调试。
/**
* 封装了对sqllite基本操作
*/
module.exports = {
dbName: 'chatapp', // 数据库名称
dbPath: '_doc/chat.db', // 数据库地址,推荐以下划线为开头 _doc/xxx.db
// 判断数据库是否打开
isOpen() {
// 数据库打开了就返回 true,否则返回 false
var open = plus.sqlite.isOpenDatabase({
name: this.dbName, // 数据库名称
path: this.dbPath // 数据库地址
})
return open;
},
// 创建数据库 或 有该数据库就打开
openSqlite() {
return new Promise((resolve, reject) => {
// 打开数据库
plus.sqlite.openDatabase({
name: this.dbName,
path: this.dbPath,
success(e) {
console.log("open sql success")
resolve(e); // 成功回调
},
fail(e) {
console.log("open database error")
reject(e); // 失败回调
}
})
})
},
// 关闭数据库
closeSqlite() {
return new Promise((resolve, reject) => {
plus.sqlite.closeDatabase({
name: this.dbName,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
})
},
/**执行增删改,
* @param {Object} sql
*/
execQuery(sql){
var db=this;
if(this.isOpen()){
return new Promise((resolve, reject) => {
plus.sqlite.executeSql({
name: db.dbName,
sql: sql,
success(e) {
console.log("sql "+ sql+" exectue ok")
resolve(e);
},
fail(e) {
reject(e);
}
})
});
}
else{
return new Promise((resolve, reject) => {
db.openSqlite().then(res=>{
plus.sqlite.executeSql({
name: db.dbName,
sql: sql,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
}).catch(res=>{
reject({code:1,msg:'open db '+db.dbName +' error'})
})
});
}
},
//查询
selectQuery(sql){
var db=this;
if(this.isOpen()){
return new Promise((resolve, reject) => {
plus.sqlite.selectSql({
name: db.dbName,
sql: sql,
success(e) {
console.log("sql "+ sql+" exectue ok")
resolve(e);
},
fail(e) {
reject(e);
}
})
});
}
else{
return new Promise((resolve, reject) => {
db.openSqlite().then(res=>{
plus.sqlite.selectSql({
name: db.dbName,
sql: sql,
success(e) {
resolve(e);
},
fail(e) {
reject(e);
}
})
}).catch(res=>{
reject({code:1,msg:'open db '+db.dbName +' error'})
})
});
}
},
/**
* 创建缓存表,
*/
createDB(){
var sql='create table if not exists temp("id" INTEGER PRIMARY KEY AUTOINCREMENT,"type" varchar(20),"val" varchar(20),"createtime" varchar(30))';
return this.execQuery(sql);
},
/**
* 创建聊天表
*/
createDBChat(){
var sql='create table if not exists chat("id" INTEGER PRIMARY KEY AUTOINCREMENT,"from" varchar(20),"to" varchar(20),"time" varchar(30),"msg" varchar(400))';
return this.execQuery(sql);
},
/**删除一个缓存
* @param {Object} id
*/
deleteTemp(id){
return this.execQuery("delete from temp where id="+id);
},
deleteByTypeVal(tp,val){
return this.execQuery("delete from temp where val='"+val+"' and type='"+type+"'");
},
getTempByType(tp){
return this.selectQuery("select * from temp where type='"+tp+"'");
},
getTempByID(id){
return this.selectQuery("select * from temp where id='"+id+"'");
},
//修改数据
updateData(dbTable,data){
//var dbTable='temp';
var db=this;
var sql="update "+dbTable+" set ";
for(var k in data){
sql+="'"+k+"'='"+data[k]+"',"
}
sql=sql.substr(0,sql.length-1)+" where id="+data['id'];
console.log(sql);
return this.execQuery(sql);
},
//增加数据
insertTemp(data){
var dbTable='temp';
var db=this;
console.log(data);
var s1="select count(*) as c from "+dbTable +" where val='"+val+"' and type='"+type+"'";
this.selectQuery(s1).then(res=>{
if(res.length>0 && res[0].c==0){
data.createtime=db.getCurrentTime();
// 判断传的参是否有值
var sql="insert into "+dbTable +"(";
var sql2=" values (";
for(var k in data){
sql+="'"+k+"',";
sql2+="'"+data[k]+"',";
}
sql=sql.substr(0,sql.length-1)+")";
sql2=sql2.substr(0,sql2.length-1)+")";
sql=sql+sql2;
//console.log(sql);
db.execQuery(sql).then(res=>{});
}
})
},
//增加数据
insertChat(data){
var dbTable='chat';
var db=this;
console.log(data);
// 判断传的参是否有值
var sql="insert into "+dbTable +"(";
var sql2=" values (";
for(var k in data){
sql+="'"+k+"',";
sql2+="'"+data[k]+"',";
}
sql=sql.substr(0,sql.length-1)+")";
sql2=sql2.substr(0,sql2.length-1)+")";
sql=sql+sql2;
//console.log(sql);
return this.execQuery(sql);
},
getCurrentTime() {
var date = new Date();//当前时间
var month = this.zeroFill(date.getMonth() + 1);//月
var day = this.zeroFill(date.getDate());//日
var hour = this.zeroFill(date.getHours());//时
var minute = this.zeroFill(date.getMinutes());//分
var second = this.zeroFill(date.getSeconds());//秒
//当前时间
var curTime = date.getFullYear() + '-' + month + '-' + day + " " + hour + ':' + minute + ':' + second;
return curTime;
},
zeroFill(t){
if(t>=10)return t+"";
else return "0"+t;
}
}
关键代码2:wsocket.js,完成socket链接,监听,发送消息
import DB from "./sqlite.js"
import store from '@/store/store.js'//引入store,有消息直接存入
export default{
url:'',//链接地址
player:'',//用于播放消息声音
setUrl(u){//外部通过这个函数,开启websocket监听
var pg=this;
console.log("ws",u);
this.url=u;
uni.connectSocket({//链接到ws
url: u,
fail:(err)=>{
console.log("error",err);
},
success(r) {
console.log("success",r)
}
});
uni.onSocketMessage(res=>{
pg.onMessage(res);//把消息交给当前对象处理
})
},
onMessage(d){
//当前对象处理消息
this.playSound();//播放声音
var msg=JSON.parse(d.data);//消息对象转换
store.commit('pushMsg',msg);//存入到store
DB.insertChat(msg);//存储到数据库
},
sendMessage(d){
//把消息发出去
uni.sendSocketMessage({
data:JSON.stringify(d)
})
},
playSound(){
//播放声音
if(this.player==''){
this.player=uni.createInnerAudioContext();//创建播放对象
this.player.autoplay = true;
this.player.onPlay(() => {
console.log('开始播放');
});
this.player.onError((res) => {
console.log("playerror",res);
});
}
this.player.src = '/static/msg.mp3';
}
}
关键代码3,store.js 全局的消息,供页面监听
//引用Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
//实例store并导出
export default new Vuex.Store({
state: {
count: 0,//用来触发消息列表更新,直接监听chats无法触发watch,
msg:{},//最后一条消息,用来给页面监听
chats:{},//存储全局的聊天列表,以用户标识为key,
users:{},//全局存储用户列表,以用户标识为key
},
mutations: {
pushMsg(state,msg){
/*消息触发 */
console.log("pushMsg",msg)
state.msg=msg;//消息改变,触发给前台
state.count++;//计数器增加,为了让页面刷新
var cs=state.chats;//先存储到一个数组
var name=msg.from;//默认是用户的标识
if(state.users[msg.from]!=null)name=state.users[msg.from].name;//显示名称使用存储的用户名
cs[msg.from]={user:name,lastmsg:msg.msg};//聊天信息,姓名及最后消息
state.chats=cs;//再保存回去
},
pushMsgTo(state,msg){
/*用户主动发送消息*/
state.msg=msg;//消息改变,触发给前台
state.count++;//计数器增加,为了让页面刷新
var cs=state.chats;//先存储到一个数组
var name=msg.to;//默认是用户的标识
if(state.users[msg.to]!=null)name=state.users[msg.to].name;//显示名称使用存储的用户名
cs[msg.to]={user:name,lastmsg:msg.msg};//聊天信息,姓名及最后消息
state.chats=cs;//再保存回去
},
setUsers(state,users){
/* 设置用户列表 ,这个是有app页面,发起请求,获取用户列表,全局存储*/
for(var i=0;i<users.length;i++){
var u=users[i];
state.users[u.id]=users[i];//更新或者存储用户信息
}
}
}
})
关键代码4,main.js 将wssocket加入原型链,启用store。
import Vue from 'vue'
import App from './App'
import store from './store/store.js'
import ws from "pages/components/wscoket.js"
Vue.config.productionTip = false
App.mpType = 'app'
Vue.prototype.$ws=ws;//挂载websocket
const app = new Vue({
...App,
store
})
app.$mount()
关键代码4,在app.vue 里面对数据库进行初始化
<script>
import DB from "pages/components/sqlite.js"
export default {
onLaunch: function() {
/**
* 数据库初始化
*/
DB.createDB().then(res=>{
console.log(res);
DB.createDBChat().then(res=>{
console.log(res);
})
})
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style>
/*每个页面公共css */
</style>