ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

案例-Node+Websocket单人聊天功能

2021-04-04 11:01:49  阅读:238  来源: 互联网

标签:Node Websocket 单人 ret tsdkClient listeners let ws any


这次我们开发一个单人聊天的案列

思路
首先整体设计上我们除了登录模块现在还需要加一个聊天模块,其次发送方需要一个发送信息的接口,接收方应该有一个新消息通知事件

实现
1、修改terminalSDK.ts(定义外部注册的新消息通知事件onEvtNewsInComing )

import tsdkClient from './tsdkClient'
import Logger from './util/logger'
export default class terminalSDK {
	public static tsdkClient;
	constructor () {
	}
	 
	public static tsdkCreateClient(initParam: any, listeners:any) {
		try{
		    terminalSDK.tsdkClient = new tsdkClient(initParam)
		}catch (err){	
			Logger.error("terminalSDK", "terminal create failed..." + err)
		}
		if(!terminalSDK.tsdkClient){
			return ""
		}

		if(listeners && listeners.onEvtLoginSuccess != 'undefined'){
			terminalSDK.tsdkClient.on("onEvtLoginSuccess", listeners.onEvtLoginSuccess)
		}
		
		if(listeners && listeners.onEvtLoginFailed != 'undefined'){
			terminalSDK.tsdkClient.on("onEvtLoginFailed", listeners.onEvtLoginFailed)
		}

		if(listeners && listeners.onEvtNewsInComing != 'undefined'){
			terminalSDK.tsdkClient.on("onEvtNewsInComing", listeners.onEvtNewsInComing)
		}

		if(listeners && listeners.onEvtWebSocketConnect != 'undefined'){
			terminalSDK.tsdkClient.on("onEvtWebSocketConnect", listeners.onEvtWebSocketConnect)
		}
		
		if(listeners && listeners.onEvtWebSocketClose != 'undefined'){
			terminalSDK.tsdkClient.on("onEvtWebSocketClose", listeners.onEvtWebSocketClose)
		}
				
		return terminalSDK.tsdkClient
	 }
}

2、修改tsdkClient.ts(新增一个对外暴露的发送消息接口以及将新消息通知事件加入到主题类中)

import tsdkManagerService from './service/tsdkManagerService'
import tsdkLoginService from './service/tsdkLoginService'
import tsdkChatService from './service/tsdkChatService'
import  Observer from './util/observer'
export default class tsdkClient {
	private static  __listeners: any = {}
	private static loginService: tsdkLoginService
	private static managerService: tsdkManagerService
	private static tsdkChatService: tsdkChatService
	
	constructor (initParam:any) {
		tsdkClient.managerService = new tsdkManagerService(initParam.svraddr, initParam.port, initParam.ssl, this.reconnectSucessCallback)
		tsdkClient.loginService = new tsdkLoginService()
		tsdkClient.tsdkChatService = new tsdkChatService()
		
		this.registerWebsocketEvent()
		this.registerLoginEvent()
		this.registerChatEvent()
	}

	reconnectSucessCallback() {
		let _this = this
		if(Observer.getReconnectStatus()){
			tsdkClient.loginService = new tsdkLoginService()
			Observer.resetReconnect()
		}
		Observer.publish("onEvtWebSocketConnect", '服务器连接成功') // 上报重连成功
	}

	// login module
	public login(loginParam: any, callback:Function) {
		tsdkClient.loginService.login(loginParam, callback)
	}

	public reconnectBindWs(userId: number, callback:Function) {
		tsdkClient.loginService.reconnectBindWs(userId, callback)
	}

	// chat module
	public singlePersonChat(chatParam: any, callback:Function) {
		tsdkClient.tsdkChatService.singlePersonChat(chatParam, callback)
	}

	public notify (event: string, data:any) {
		let _listen = tsdkClient.__listeners[event]
		if(!_listen){
			return false
		}
		for(let i = 0;i < _listen.length; i++){
			typeof _listen[i] === 'function' && _listen[i](data)
		}
	}
	
	public on(event:string, callbacks:Function) {
		if(!tsdkClient.__listeners[event]){
			tsdkClient.__listeners[event] = []
		}
		tsdkClient.__listeners[event].push(callbacks)
	}

	public registerLoginEvent() {
		Observer.subscribe('onEvtLoginSuccess', (ret:any) => {
			this.notify("onEvtLoginSuccess", ret)
		})
		
		Observer.subscribe('onEvtLoginFailed', (ret:any) => {
			this.notify("onEvtLoginFailed", ret)
		})
	}

	public registerWebsocketEvent() {
		Observer.subscribe('onEvtWebSocketConnect', (ret:any) => {
			this.notify("onEvtWebSocketConnect", ret)
		})
		
		Observer.subscribe('onEvtWebSocketClose', (ret:any) => {
			this.notify("onEvtWebSocketClose", ret)
		})
	}

	public registerChatEvent() {
		Observer.subscribe('onEvtNewsInComing', (ret:any) => {
			this.notify("onEvtNewsInComing", ret)
		})
	}
}

3、聊天模块service层tsdkChatService.ts

import tsdkChatWrapper from '../wrapper/tsdkChatWrapper'
import Observer from '../util/observer'
import Logger from '../util/logger'
export default class tsdkChatService{
	private wrapper: tsdkChatWrapper
	
	constructor() {
		this.wrapper = tsdkChatWrapper.getInstance()
		this.wrapper.build()
		this.registerChatEvent()
	}
	
	public async singlePersonChat(chatParam: any, callback: Function){
		let retData = await this.wrapper.singlePersonChat(chatParam);
		callback(retData)
	}
	
	public registerChatEvent() {
		Logger.info("tsdkChatService","registerChatEvent")
		this.wrapper.registerChatEvent({
			onEvtNewsInComing: tsdkChatService.handleOnEvtNewsInComing
		})
	}
	
	public static handleOnEvtNewsInComing(ret){
		Observer.publish("onEvtNewsInComing", ret)
	}
}

4、聊天模块wrapper层tsdkChatWrapper.ts

import tsdkChat from '../json_adapt/tsdkChat'
import tsdkManagerWrapper from './tsdkManagerWrapper'
import Logger from '../util/logger'
export default class tsdkChatWrapper{
	private static tsdkChat: tsdkChat
	private static wrapper: tsdkChatWrapper = new tsdkChatWrapper()
	
	constructor() {
		if(tsdkChatWrapper.wrapper){
			 throw Error("tsdkChatWrapper has exist")
		}
		tsdkChatWrapper.wrapper = this
	}
	
	public build () {
		Logger.info("tsdkChatWrapper","tsdkChatWrapper has build")
		tsdkChatWrapper.tsdkChat = new tsdkChat({
			socket: tsdkManagerWrapper.socketService
		})
	}
	
	public static getInstance () {
		return tsdkChatWrapper.wrapper
	}
	
	public singlePersonChat (chatParam: any) {
		let callback = { response: {} }
		let promise = new Promise((resolve, reject) => {
			callback.response = (ret: any) => {
				resolve(ret)
			}
		})
		tsdkChatWrapper.tsdkChat.singlePersonChat(chatParam, callback)
		return promise
	}
	
	public registerChatEvent(callbacks:any) {
		tsdkChatWrapper.tsdkChat.setBasicEvent(callbacks)
	}
}

5、聊天模块数据发送层tsdkChat.ts


export default class tsdkChat{
	private serviceTunnel: any
	
	constructor(opt) {
		this.serviceTunnel = opt.socket
	}
	
	sendData(data) {
		let dataStr = JSON.stringify(data)
		if(this.serviceTunnel.socket){
			this.serviceTunnel.sendData(dataStr)
		}
	}
	
	callbackResponse(callback: any, rsp:number) {
		if(callback.response && typeof callback.response === 'function'){
			this.serviceTunnel.rspFuncs[rsp] = callback.response
		}
	}
	
	//cmd 3001
	singlePersonChat(chatInfo: any, callback) {
		this.callbackResponse(callback, 3001)
		
		let data = {
			"cmd": 3001,
			"description": "tsdk_single_person_chat",
			"param": {
				"chatInfo": chatInfo
			}
		}
		
		this.sendData(data)
	}
	
	setBasicEvent(callbacks:any) {
		if(typeof callbacks.onEvtNewsInComing === 'function') {
			this.serviceTunnel.notifyFuncs[3002] = callbacks.onEvtNewsInComing
		}
	}
}

当前的目录结构
在这里插入图片描述
打包
项目根目录下执行npm run build 进行打包
在这里插入图片描述
服务端
接下来进行服务端功能的开发
1、Test\server\jsonAdapt目录下新建chat文件夹,在chat文件夹下新建tsdk_chat_cmd.js、tsdk_chat_notify.js文件
tsdk_chat_cmd.js

const { _makeMsgSetAndSend } = require('../../util/util');
const Tunnel = require('../../util/Tunnel');
const ChatService = require('./tsdk_chat_notify');
class ChatController {
	/*
	chatParam: {
	   target // 发送对象
	   message
	} */
	static async singlePersonChat(chatInfo, ws) {
		let ret 
		let targetWs = Tunnel.getUserWs(chatInfo.target)
		if(targetWs==null){
			ret = _makeMsgSetAndSend(3001, 10002, "tsdk_single_person_chat")
			ws.send(ret)
			return false
		}
		
		let originUser = Tunnel.searchUserByWs(ws)
		let messageInfo = {
			origin: originUser,
			content: chatInfo.message
		}
		let jsonValue = ChatService.onEvtNewsInComing(messageInfo)
		ret = _makeMsgSetAndSend(3001, 0, "tsdk_single_person_chat")
		ws.send(ret)
		targetWs.send(jsonValue)
	}
	
}

module.exports = ChatController;

tsdk_chat_notify.js

const { _makeMsgImport } = require('../../util/util');
class ChatService {
	
	// 3002
	static onEvtNewsInComing (messageInfo) {
		let param = {  
			message: messageInfo
		}
		return _makeMsgImport(3002, param, "ON_EVT_NEWS_INCOMING")
	}
}

module.exports = ChatService;

2、修改Test\server\util下Tunnel.js

class Tunnel{
	static users = {}
	static online_sum = 0 // 在线总人数

	static bindWsByUserId(userId, ws) { // 将userId、用户的连接进行绑定
		if(!Tunnel.users[userId]){
			Tunnel.users[userId] = {}
			Tunnel.online_sum++
		}
		Tunnel.users[userId].ws = ws
	}
	
	static getUserWs(userId) { // 根据userId返回用户websocket连接对象
		if(!Tunnel.users[userId]){
			return null
		}
		return Tunnel.users[userId].ws
	}

	static searchUserByWs(ws) { // 通过socket连接查找对应userId
		let allUsers = Object.keys(Tunnel.users)
		for(let i =0; i< allUsers.length; i++) {
			let userId = allUsers[i]
			if(Tunnel.users[userId].ws == ws) {
				return userId
			}
		}
		return -1;
	}
}
module.exports = Tunnel;

3、修改Test\server\util目录下util.js

const ErrorCode = {
	10001: '参数错误',
	10002: '消息发送失败'
}

// 接口调用的返回格式
function _makeMsgSetAndSend (rsp, code, description) {
	let JsonParam = {
		 description: description,
		 result: code == 0? code : {errorCode: code, reason: ErrorCode[code]},
		 rsp: rsp,
	}
	return JSON.stringify(JsonParam)
}

// 接口回调或通知事件的返回格式
function _makeMsgImport(notify, result, description){
	let JsonParam = {
		 description: description,
		 result: result,
		 notify: notify,
	}
	return JSON.stringify(JsonParam)
}

module.exports = { _makeMsgSetAndSend, _makeMsgImport }

4、修改Test\server下的app.js

var express = require("express");
var http = require('http');
var fs = require('fs');
var WebSocket = require('ws');
var app = express();

app.get('/', function(req, res){
	res.send('Hello,myServer'); //服务器响应请求
});

const LoginController = require('./jsonAdapt/login/tsdk_login_cmd');
const ChatController = require('./jsonAdapt/chat/tsdk_chat_cmd');

var httpServer = http.createServer(credentials, app);
const PORT = 3000;
const hostname = '0.0.0.0';
httpServer.listen(PORT,hostname, function() {
    console.log('Websocket Server is running on: http://'+hostname+':%s', PORT);
});

var wss = new WebSocket.Server({server: httpServer});
wss.on('connection', function connection(ws) {
	console.log('链接成功!');
	ws.on('message', async function incoming(data) {
		let message = JSON.parse(data)
		console.log(message)
		await jsonAdapt(message, ws)
   });
});

function  jsonAdapt(message, ws) {
	switch (message.cmd) {
		case 1001:
		    LoginController.login(message.param.loginParam, ws)
			break;
		case 1002:
			LoginController.reconnectBindWs(message.param.userId, ws)
			break;
		case 3001:
			ChatController.singlePersonChat(message.param.chatInfo , ws)
			break;
	}
}

5、启动服务器 node app.js
在这里插入图片描述
前台
修改Test\html目录下的index.html,新建index2.html(与index.html页面除了登陆账号不同其余内容相同,用于创建单人聊天场景)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>websocketSDK</title>
    <script src="../../build/terminalSDK.js"></script>
    <script>
        let initParam = {
            svraddr: '127.0.0.1',
            port: 3000,
            ssl: 0 // 0:http, 1:https
        }
        let userInfo = {} // 登陆成功后保存用户信息
        let isReconnect = false // 重连状态标识
        let listeners = {
            onEvtLoginSuccess: (ret) => {
                console.log(ret)
                userInfo = ret.result.userInfo
            },
            onEvtLoginFailed: (ret) => {
                console.log(ret)
            },
            onEvtWebSocketConnect: (ret) => {
                console.log(ret)
                if(isReconnect) { // 重连成功
                    isReconnect = false
                    tsdkClient.reconnectBindWs(userInfo.userId, (ret) => {
                        console.log( ret)
                    })
                }
            },
            onEvtWebSocketClose: (ret) => {
                console.log(ret)
                isReconnect = true
            },
            onEvtNewsInComing: (ret) => {
                console.log('新消息:', ret)
            }
        }
        window.tsdkClient = terminalSDK.tsdkCreateClient(initParam, listeners)

        function login() {
            let loginParam = {
                username: '张三',
                pwd: '123456'
            }
            tsdkClient.login(loginParam, (ret) => {
                console.log( ret)
            })
        }

        function send() {
            let chatParam = {
                target: 2, // 接收方userId
                message: '你好'
            }
            tsdkClient.singlePersonChat(chatParam, (ret) => {
                console.log( ret)
            })
        }
        
    </script>
</head>
<body>
    <button onclick="login()">登录</button>
    <button onclick="send()">发送消息</button>
</body>
</html>

浏览器访问index.html、index2.html
在这里插入图片描述
在这里插入图片描述
备注:index.html页面登陆账号为发送方,index2.html页面登陆账号为接收方
在这里插入图片描述
备注:对方不在线场景,返回相应提示

思考
以上是单人聊天功能的实现,同样如果是群聊,我们通过数据库查询出群内所有成员,然后查找出这些成员的socket连接集,给这些成员发送消息,这块就不一一实现了,感兴趣的话可以自己尝试一下。

标签:Node,Websocket,单人,ret,tsdkClient,listeners,let,ws,any
来源: https://blog.csdn.net/weixin_42246659/article/details/115426079

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有