关于vue在实时聊天的app使用的一些总结(前端)

背景

最近开发了一款实时聊天的app,主要功能包括:
1. 注册
2. 登录
3. 个人中心管理(包括密码、昵称、头像等)
4. 好友管理
5. 群聊天管理
6. 实时聊天(群、好友)
7. 消息提醒推送

技术栈

前端:
- vue (前端框架)
- vuex (全局数据管理)
- muse-ui (谷歌的一款ui框架,感觉做聊天挺不错)
- axios (请求后台数据)
- websocket (实时通讯)
- better-scroll (滑动效果)

项目结构

关于vue在实时聊天的app使用的一些总结(前端)_第1张图片
build、config…这些不用说太多,想必大家使用过vue-cli都有所了解了。主要谈谈src下的结构吧
- assets 存放一些打包后需要使用的一些静态图片
- css 这里面我只放了一个reset.css
- pages 所有的页面开发都将放在这里面
- plugins 自己写的一些插件,这个项目中仅有Loading以及Toast
- router 路由文件夹, 这里面我放了两个一个是routes.js 和index.js,routes.js 主要是管理所有的路由,对所有路由在这里进行加载export的对象用于在index.js的new Router方法所需要传入的routes参数,这里我是这样理解的,router、routes两个名字的概念是不同的,router指的是路由管理者主要对路由进行一些配置等,而routes指的是一组路由信息,当然route就是单一的一个路由信息,所以我会在index.js中写一些对Router对象的配置,比如写一些公用的方法例如goBack回退方法挂载在router.prototype上。
- server 服务请求文件夹, 其中也分为两个文件,一个是index.js 一个是commonServices.js, index.js是对aioxs的一些默认配置比如timeout、baseURL等,而commonServices.js主要是写了一些公用的处理方法,对数据的取、请求等。
- store vuex管理文件夹
- utils 公用方法文件夹 包括localStorage.js 、 Websocket.js、 dateFormat.js

好吧,我想有的童鞋可能会问干嘛建那么多文件夹,比如公用的方法写在一个utils.js文件里不就好了么,嗯其实也不是不可以,只是我个人比较喜欢每个文件的代码量都尽量精简一些,需要什么直接import就好而不是把所有的东西都放在一个文件里面,比如我把utils里的文件的方法都提取到一个文件里面,因为是一个团队开发,如果你写了某个新方法没有在文件开头写好注释别人就不一定知道你多加了一个方法,我上一家公司就是这样,当时是使用的ng1.0+,都是前年的事了,一个公用方法文件里放了n个人的公用方法,然后一个文件的代码量就有几千行。当时也是
多人共同开发一个项目,如果今天两个人都改了这个文件里的东西然后到晚上一提交代码就发生冲突,如果我们做好模块化尽量让单个方法一个文件这样既不会发生冲突,并且如果一个人新写了一个方法也不用打开文件去开就知道xx新写了一个方法,岂不是美滋滋
当然如果你是一个开发一个项目那么你想怎么玩儿就怎么玩儿~

具体功能实现

  1. 注册、登录: 我想这个不用讲太多吧,使用v-model绑定数据,然后调用axios请求后台数据就行了
  2. 个人中心管理(包括密码、昵称、头像等): 这个模块的话我是在用户登录后将用户的一些相关信息都存入vuex中, 因为app端的话 一般会把修改昵称、密码、手机等都分为多个页面,所以在每个页面都可能会用到一些用户的相关信息,那么使用vuex的核心就是在多个组件页面中会用到共享的数据,那么就将这些数据放在vuex中来进行管理。至于头像,好吧这个app我们不是用户自定义上传头像,是qq最开始本地存一些图片,然后用户信息中保存图片相应的url,来读取本地图片的。
  3. 好友管理、群聊天室管理: 这里包括了群和好友的增、删、改、查,个人觉得做过信息系统的应该也不会有什么大的难度吧,需要注意的就是数据的绑定,建议把当前好友信息以及当前群信息也放在vuex中进行管理,因为拿好友举例,查询到了好友在用户信息显示页面会用到好友信息,然后添加后进入聊天室也会用到好友信息,进行备注也会用到好友信息,因此建议还是放在vuex中进行一个统一的管理。其次就是回退方法,比如进入好友聊天室页面,可能是通过:
    • 好友列表->好友聊天室
    • 聊天记录列表 ->好友聊天室
    • 好友备注 ->好友聊天室
    • 添加好友 ->好友聊天室
      那么我们就不能统一的使用window.history.go(-1)来回退,而是要进行分别的处理,那么这里就需要用到vue-router中的from对象了,我是在每次进入该路由的时候对进入的from进行记录,存到data中,我也试过在click回退的时候使用this.$router.beforeEach()好像没进这个方法,好吧仔细想了一下因为这个时候路由本来也没有变化所以自然也不会进入这个方法咯,如果要用这个方法应该写在created钩子中,进入路由的时候记录from,我最开始希望点击回退按钮的时候直接检查进入时候的from来进行跳转,好像并不能直接这样写来实现。
  4. 实时聊天(群、好友) 、消息提醒推送,本来是想使用socket.io来实现的,但是后台好像没有对应的解决,网上大多都是使用nodejs来写服务器端的,好吧所以只有自己手撸一个websocket.js的轮子了,这里安利一波阮一峰老师的websocket博客: http://www.ruanyifeng.com/blog/2017/05/websocket.html ,用过socket.io后再用websocket只能说,好他么难用…websocket本身对send方法没有回调,send之后你也不知道是否发送成功与否,服务器给予的返回都在onmessage方法中,无论任何返回都在onmessage方法中进行监听,所以可以想象一下,我先发送了一个1,然后发送了一个2,都发送成功,服务器会有2个返回,可能第一个返回是针对发送的2的返回;再如果先发送了一个bar,接着又发送了一个bar,这时服务器只有一个返回,那么到底是第一个发送成功了还是第二个发送成功了呢? 我们的解决方法是,在发送时传给服务器端一个date以及发送的用户信息,然后服务器端进行返回,那么这样就能判断唯一值了。消息推送也采用websocket来实现,具体的websocket.js如下(这是一个破轮子,不具有代表性,目前只适合这个项目,哈哈,不过还是放出来希望给大家一些灵感):
import store from '@/store' //vuex

if(!'WebSocket' in window){
    alert("当前浏览器不支持在线聊天功能,请更换版本较新的浏览器")
}

let ws //全局websocket对象
const baseURL = '...',
      bindFunc = (cntor, model) =>{
    if(!ws)
        return
    ws.onopen = (res) =>{
        console.log('链接成功')
    }

    ws.onmessage = (res)=>{
        console.log('接收信息成功')
        let result = JSON.parse(res.data)
        //先判断这条信息是否是这个人发送的, 是则再将对应的消息设置发送状态为成功,不是则直接将信息push到history中,其他则为消息推送
        if(result.msgFrom == cntor.username){
            for(let i = model.length - 1; i > -1 ; i--){
                if(model[i].message == result.msg 
                    && model[i].date - new Date(result.date) == 0){
                    //找到发送者发送的该条信息
                    model[i].status = 'success'
                    break
                }
            }
        }else if(model){
            model.push({
                username: result.msgFrom,
                message: result.msg,
                groupId: result.groupId,
                date: new Date(result.date)
            })
        }else{
            //主要未读消息提醒
            //先清空未读消息

            store.commit('addUnReadCount', result.count) //添加总的未读消息数
            store.commit('addSelfUnReadCount', { //设置单个聊天室的未读消息
                msgFrom: result.msgFrom,
                count: result.count
            })  
        }
    }

    ws.onclose = (res)=> {
        console.log('链接已被关闭')
    }

    ws.onerror = (err) =>{
        console.log(err)
    }
}

export default {
    connect({url, params, model, connector}){
        const cntor = deepClone(connector)

        url? url = baseURL + '/' + url : url = baseURL
        if(JSON.stringify(params) != '{}'){
            url += '?'
            for(let [index, elem] of Object.entries(params)){
                url = url + index + '='  + elem + '&'
            }
            url = url.substring(0, url.length - 1)
        }


        if(ws){
            switch(ws.readyState){
                case 0 || 1://正在连接、连接成功
                let timer1 = setInterval(()=>{
                    console.log('关闭ws请求发送ing...')
                    ws.close()
                    if(ws.readyState == 3){
                        clearInterval(timer1)
                        ws = new WebSocket(url)
                        store.commit('resetUnReadInfoNum')
                        bindFunc(cntor, model)

                    }
                }, 500)
                break

                case 2://正在关闭
                let timer2 = setInterval(()=>{
                    ws.close()
                    if(ws.readyState == 3){
                        clearInterval(timer2)
                        ws = new WebSocket(url)
                        store.commit('resetUnReadInfoNum')
                        bindFunc(cntor, model)
                    }
                }, 500)
                break

                default: 
                ws = new WebSocket(url)
                store.commit('resetUnReadInfoNum')
                bindFunc(cntor, model)
            }
        }else{
            store.commit('resetUnReadInfoNum')
            ws = new WebSocket(url)
            bindFunc(cntor, model)
        }
    },
    send(msg) {
        if(!ws || ws.readyState != 1)
            return '当前不存在websocket链接信息'

        ws.send(JSON.stringify(msg))
    },
    close() {
        if(ws.readyState == 1)
            ws.close()
    },
}

最后的最后

对于多人开发还有一个想法,一般多个人开发一个项目都是分模块来开发的,一个人搭建好总的环境后,每个模块都是放在pages中,然后每个模块建一个文件夹,这个模块文件夹中分为view、controller、services,例如login:

login.vue:





loginController.js:

import services from './services
 export default {
    data: ()=>({ 
       //...
    }),
    methods: {}
 }

loginServices.js:

import commonServices from '@/server/commonServices'
export default {
    //...
}

对于路由文件routes.js,每个人都可以建一个自己的routes.js文件,前缀加自己的名字驼峰写法就好,因为这样写每次一个人新写一个模块的时候就不需要修改routes.js文件了,修改的也只是自己的routes文件,提交的时候就不会引起冲突了。在routes.js中:

import aRoutes from './aRoutes'
import bRoutes from './bRoutes'
export default [...aRoutes, ...bRoutes]

忙里偷闲写了这篇博文,因为是写博客的新手,所以有很多不足的地方大家随便提,我有空就改~~

关于vue在实时聊天的app使用的一些总结(前端)_第2张图片

你可能感兴趣的:(vuejs)