基于Vue+Axios+NodeJS+Express+Socketio+TypeScript实现的vue在线聊天室——后台部分
-
在一个月前,我们讨论了WebSocket的基本用途和基本用法。现在我们将使用SocketIO来实现在线聊天室的后台功能。使用SocketIO的原因是这个库是WebSocket的超集,能够兼容更多的浏览器,在使用方面上也比WebSocket更加的灵巧、方便。
开发思路
针对需求,我们可以设计出如下的运行流程:
- 客户端先展示登录界面,输入用户名与密码,此时暂定用户名与密码是相同的。
- 密码正确后进入选择房间的页面,点击其中一个房间的进入按钮,即可进入聊天室参与多人聊天。
经过分析,我们可以得到这样的开发思路:我们可以使用http库实现用户的登录与房间的进入,使用SocketIO库来实现多人在线聊天功能。这样我们可以整理出如下的路由以及通信事件:
- http路由:
login
:用来管理用户输入的账号密码
roomlist
:用来显示房间列表 - SocketIO通信事件:
login
:实现进入房间
sendMessage
:实现消息的发送
disconnect
:实现退出房间
以上就是这个在线聊天室的基本开发思路。在线聊天室的逻辑其实并不复杂,本文的主要目的就是为了实现TS、JS的混合编程。现在我们来看看如何使用TypeScript结合NodeJS来开发后台。
TypeScript
相较于JavaScript来说,TypeScript会有更加严谨的变量声明与参数类型限制,另外还增加了Java、C#中OOP的概念,比如加入了泛型、多态、接口等面向对象的概念,建议查看TypeScript的相关文档或视频,并且对JavaScript有一定基础的再来看这次的内容。
首先,我们先安装TypeScript。在工程文件夹下输入npm install -g typescript
命令即可全局安装TypeScript。
接下来我们运行自定义编译进程。我这次使用的是VSCode作为IDE,在工程文件夹下输入tsc --init
可以生成tsconfig.json
文件。将outDir
属性写为"./js"
,意思是将ts编译出来的js放在js文件夹中,并且可以按照目录来更新。然后在compilerOptions
外面添加一个同级属性exclude
,其内容是"[node_modules]"
。代码如下所示://tsconfig.json { "compilerOptions": { "target": "es5", "module": "commonjs", "outDir": "./js", "strict": true, "esModuleInterop": true }, "exclude": [ "node_modules" ] }
在终端窗口点击运行任务,选择
watch(监视)
,即可开启进程。watch
的目的是可以在ts保存修改后实现自动编译。
如果出现error TS5058: The specified path does not exist: 'd:我的网页chatOnlineservertsconfig.json'.
终端进程已终止,退出代码: 1在命令行中直接启动
tsc -p "./tsconfig.json" --watch
即可。后台实践
在这次实践中,我们将会定义三个类:
Hall
、Room
、RoomMember
。Hall
是大厅类,Room
是房间类、RoomMember
是成员类。接下来我使用TypeScript先实现这三个类的属性、方法的定义以及实现。
先是RoomMember
类://RoomMember.ts import {Room} from "./Room" //同于ES6的引入规范,不加ts后缀 class RoomMember { private id:string; //私有变量 private username: string; //定义类型string enterTime: Date = new Date(); //Date类型,没有private默认为public共有变量 enterRoomTime: Date = new Date(); inWhichRoom:Room | undefined = undefined; //定义类型Room类或undefined socket:any; //可以为任意类型 constructor(id:string,username:string,socket:any=undefined){ //RoomMember的构造函数,socket为缺省值 this.id = id; this.username = username; this.socket = socket; } isInRoom():boolean { //函数返回是boolean类型 if(this.inWhichRoom===undefined){ return false; } else { return true; } } getUserName():string{ return this.username; } getId():string{ return this.id; } getEnterRoomTime():Date{ return this.enterRoomTime; } getSocket():any{ return this.socket; } sendMsgToAnotherMember(id:string,event:string,msg:any):boolean{ if(id===this.id){ return false; } this.socket.emit(event,msg); //向指定用户发送信息msg return true; } } export {RoomMember}; //同ES6的导出方法
然后是
Room
类://Room.ts import {RoomMember} from './RoomMember'; class Room { private id:string; private name:string; private createTime:Date=new Date; private lastTime:Date=new Date(); private memberList:RoomMember[]=[]; //定义RoomMember数组类型 public desc:string=''; constructor(id:string,name:string=''){ this.id = id; this.name = name; } getId():string{ return this.id; } getName():string{ return this.name; } getMemberNum():number{ return this.memberList.length; } getMemberList():RoomMember[]{ return this.memberList; } getCreateTime():Date{ return this.createTime; } getLastTime():Date{ return this.lastTime; } addMember(member:RoomMember):boolean{ member.enterRoomTime = new Date(); member.inWhichRoom= this; //成员的所在房间指向this this.memberList.push(member); //将用户添加进房间成员数组中 this.lastTime = new Date(); return true; } deleteMember(id:string):boolean{ let member_num:number = this.getMemberNum(); for(let i=0;i<member_num;i++){ if(this.memberList[i].getId()===id){ this.memberList[i].inWhichRoom = undefined; this.memberList.splice(i,1); this.lastTime = new Date(); return true } } return false; } sendMsgToAllRoomMember(event:string, msg:any):boolean{ this.memberList.forEach(member => { member.getSocket().emit(event,msg); //向房间内所有用户发送信息msg }) return true; } } export {Room};
最后的
Hall
你们来实现,其实也是十分简单的。
接下来实现TCP通信功能,首先我们先安装express
、socketio
,一般都是npm install <名字>
来安装,如果出现需要安装@type
版本的,那就安装那个。之后会生成package.json
文件,dependencies
属性为://package.json //... "dependencies": { "@types/express": "^4.17.1", "@types/socket.io": "^2.1.2", "express": "^4.17.1", /* 可以删除 */ "socketio": "^1.0.0" /* 可以删除 */ }, //...
然后我们实现http的监听和socketio的监听。
//httpInit.ts import express from "express"; import http from 'http'; import io from 'socket.io'; let ExpressApp:any = undefined; let HttpServer:any = undefined; function HttpInit(PORT:number):any{ ExpressApp = express(); ExpressApp.use(express.json()); //解决越界问题 ExpressApp.all('*', function (req:any, res:any, next:any) { res.header('Access-Control-Allow-Origin', '*'); //Access-Control-Allow-Headers ,可根据浏览器的F12查看,把对应的粘贴在这里就行 res.header('Access-Control-Allow-Headers', 'Content-Type'); res.header('Access-Control-Allow-Methods', '*'); res.header('Content-Type', 'application/json;charset=utf-8'); next(); }); HttpServer = http.createServer(ExpressApp); HttpServer.listen(PORT); return ExpressApp; } function SocketIOInit():any{ if(HttpServer===undefined){ console.error("HttpServer haven't been inited !"); return null; } return io(HttpServer); } export {HttpInit,SocketIOInit};
最后就是实现前后台交互的逻辑了。
//app.ts import { HttpInit,SocketIOInit } from "./httpInit"; import { Hall } from "./modules/Hall"; import {Room} from "./modules/Room"; import {RoomMember} from './modules/RoomMember'; let PORT = 8081; let ExpressApp = HttpInit(PORT); console.log("app listen at " + PORT); let io = SocketIOInit(); let hall = new Hall(); hall.addRoom('0','room0'); hall.addRoom('1','room1'); //现在先创建两个房间 /**************************HTTP************************/ //用户输入登录 ExpressApp.post('/login',(req:{body:{username:string,password:string}},res:any)=>{ if(req.body.password===req.body.username){ let newUser = new RoomMember(req.body.username,req.body.username); if(hall.addMember(newUser)){ res.send({success:true}); } } else { res.send({success:false}); } }) //用户获取房间列表 ExpressApp.get('/roomlist',(req:any,res:any)=>{ let sendData = { roomlist : hall.getRoomList().map(room => { return { id: room.getId(), name: room.getName(), member_num: room.getMemberNum(), create_time: room.getCreateTime(), last_time: room.getLastTime(), description: room.desc }; }) }; res.send(sendData); }) /***************************SOCKET************************/ //客户端创建socket连接 io.on('connection',function(socket:any) { let user:RoomMember let room:Room; //客户端进入房间 socket.on('login', function(data:{username:string,room_id:string}){ console.log('login', data); for(let i=0;i<hall.getRoomList().length;i++){ if(hall.getRoomList()[i].getId()===data.room_id){ room = hall.getRoomList()[i]; break; } } for(let i=0;i<hall.getUserNum();i++){ if(hall.getUserList()[i].getId()===data.username) { user = hall.getUserList()[i]; user.socket = socket; break; } } if(room.addMember(user)){ console.log('newUser', user.getUserName(),' enter '+data.room_id); room.sendMsgToAllRoomMember('add',{username:user.getUserName()}) //给所有房间内成员发送本成员进入 socket.emit('loginSuccess', {userlist: room.getMemberList().map(user => { return { username: user.getUserName() }; //给本成员发送所有房间成员列表 })}); } else { socket.emit('loginFail', {}); } }); //客户端发送信息 socket.on('sendMessage', (data:any) => { socket.emit('sendMessageSuccess', {}); room.sendMsgToAllRoomMember('receiveMessage',data); }); //客户端离开房间 socket.on('disconnect',()=>{ console.log('disconnect', user.getUserName()); room.deleteMember(user.getId()); room.sendMsgToAllRoomMember('leave',user.getUserName()); }) })
写完代码记得保存。这时在文件目录下系统自动生成js目录,里面就是编译好的ES5代码。
这时聊天室后台部分基本完成。关于测试方面留到前端部分再讲。
总体来说,在线聊天室的逻辑并不复杂,但是三个类的数据结构还是需要花费一定心思的,但也不是什么大问题。通过这次的实践,我们可以抽离出这三个类,实现任意的关于房间的项目,比如棋牌室游戏、大富翁游戏等等的基于房间的小游戏,有了一定的基础后,开发这类游戏就简单上手了。
大家学到了什么,有什么问题都可以来交流交流哦。o(*≧▽≦)ツ┏━┓