最近想作一個Node.js
的應用,以把本身學到的Node.js
技能初步落實一下,思前想後仍是作一個小型聊天應用吧,博客之類的恐怕後期精力不夠,(以前作phper的時候作博客就由於越想越多的功能半途放棄了😅),由於以前沒有作過WebSocket
相關的業務,因此也想在這方面實踐一下,預想的是作一個具有單聊和羣聊,簡易朋友圈等等的一些小功能,同時也由於以前沒用過mongo
,因此也想在這方面實踐一下。php
總之這個小應用主要就是我的初探Node「全棧」的初步試水,也想分享給跟我同樣尚且還菜菜的前端。html
初步須要實現的功能前端
註冊&登陸必須的node
將當前用戶online/offline信息通知給本人及其餘用戶git
區分來自client的消息類型,上線信息、消息等github
將消息發送給目標用戶web
初步實現兩個用戶之間的對話mongodb
暫且先假定有用戶user1,user2 實現這兩個用戶之間的對話數據庫
express 4.17.1express
glob 7.1.6
mongoose 5.9.14
ejs 3.1.2
nodemon 2.0.4
ws 7.3.0
const ws = new WebSocket('ws://192.168.31.200:8000') //鏈接到websokect server let userInfo = JSON.parse(sessionStorage.getItem("userInfo")); //當前用戶信息 const JSONToString = function(json) { return JSON.stringify(json) } //與server鏈接,將當前用戶信息提交給server ws.onopen = () => { // ws.send('我上線啦'),上線時只須要把如下信息給server就基本知足啦 ws.send(JSON.stringify({ sender: userInfo.name })) } //接收server的消息 ws.onmessage = (msg) => { //根據server返回的msg類型處理相關邏輯,通知其餘用戶,渲染消息等 //msg.msgType分爲 notice message //TODO } //server通訊錯誤處理 ws.onerror = err => { console.log(err) //TODO } //下線邏輯 ws.onclose = () => { ws.send(JSON.stringify({ sender: userInfo.name, receiver: userInfo.name == 'user1' ? 'test2' : "user1", message: msg })) console.log('close') } //給server發送消息,其餘事件調用此方法 function sendMsgToServer(msg) { // msg 暫定格式 //{ // sender: userInfo.name, // receiver: receiver, // message: msg 注意這裏比上面第一次onopen多了message //} ws.send(JSONToString(msg)) } 複製代碼
目錄結構
---common
|---function.js
---db
|---mongo.conf.js
--- routes
|---user.js
---views
|---login.html
|---chating.html
app.js
//app.js const express = require('express') const app = express() const glob = require("glob"); require('./routes/chats') const { resolve } = require('path'); app.listen(3000) console.log('服務已啓動') 複製代碼
//app.js /* express.js: 配置引擎 */ app.set('views', './views'); // 添加視圖路徑 app.engine('html', require('ejs').renderFile); // 將EJS模板映射至".html"文件 app.set('view engine', 'html'); // 設置視圖引擎 /* express.js: 配置引擎 */ glob.sync(resolve('./views', "**/*.html")).forEach((item, i) => { let htmlRelativePath = item.split('/views')[1] let pagePath = htmlRelativePath.replace('.html', '') app.get(pagePath, function (request, response) { let viewPath = pagePath.replace('/', '') response.render(viewPath) }) }) 複製代碼
//app.js app.use(express.json()) app.use(express.urlencoded({ extended: true })) 複製代碼
//app.js const userRouter = require('./routes/user') app.use('/', userRouter) 複製代碼
const mongoose = require('mongoose') // 引入 mongoose const url = "mongodb://localhost:27017/chat"; // 本地數據庫地址 mongoose.connect(url) const db = mongoose.connection; db.on('error', console.error.bind(console, 'connection error:')); db.once('open', function () { console.log("Successful connection to " + url) }); var Schema = mongoose.Schema let user = { name: String, password: String, headImg: String } var userSchema = Schema(user) var User = mongoose.model('users', userSchema); //將schema編譯爲model構造函數 module.exports = { mongoose, User } //這個配置目前尚且略顯簡陋,後面再改造😥 複製代碼
//user.js const express = require('express') const router = express.Router() const ObjectID = require('mongodb').ObjectID; const { sendJson, throwError } = require('../common/function') const { mongoose, User } = require("../db/mongo.conf") //先不要在乎這麼土的寫法,由於這時候我只關注主體功能😅 const checkUserExit = function (params) { return new Promise(function (resolve, reject) { User.findOne(params, function (error, res) { if(res) { resolve(res) } }) }) } //註冊 router.post('/register', function (request, response) { let params = request.body const user = new User(params) checkUserExit({ name: params.name }).then(res => { if (res) { response.send(sendJson(0, '用戶名已存在')) } else { user.save(function (error, res) { if (error) { response.send(throwError()) } else { response.send(sendJson(1, '註冊成功')) } }) } }) }) //登陸 router.post('/login', function (request, response) { let params = request.body User.findOne({ name: params.name }, function (error, res) { if (!res) { response.send(sendJson(0, '用戶不存在')) } else { if (params.password != res.password) { response.send(sendJson(0, '用戶名或密碼錯誤')) } else { response.send(sendJson(1, '用戶驗證成功',params)) } } }) }) module.exports = router 複製代碼
//function.js const getJsonStr = function (params) { return JSON.stringify(params) } function sendJson(status, msg, data, params) { return getJsonStr({ status: status, message: msg, data: data || null }) } function throwError(params) { return getJsonStr({ status: 0, msg: 'Service error' }) } module.exports.sendJson = sendJson module.exports.throwError = throwError 複製代碼
const webSocket = require('ws'); //引入ws服務器模塊 const ws = new webSocket.Server({ port: 8000 }); //建立服務器,端口爲8000 const { JSONToString, getTime } = require('../common/function') var clients = {} //記錄當前在線用戶信息 var userList = [] //僅存儲當前在線用戶名 複製代碼
ws.on('connection', (client) => { //鏈接客戶端 // 用戶上線 client.on('message', (msg) => { let userMsg = JSON.parse(msg) let { sender, receiver, message } = userMsg client.name = sender; Observer() // 實時更新基礎數據 if (message) { //數據發送輸出 sendMessageToClient(sender, receiver, message) } else { // 通知上線 noticeOnlineOrOffLine(sender, true) } }) //報錯信息 client.on('error', (err => { if (err) { console.log(err) //還沒想好作哪些處理 } })) // 下線信息 client.on('close', () => { console.log('用戶' + client.name + '關閉了消息服務') noticeOnlineOrOffLine(client.name, false) }) }) 複製代碼
/** * * @param {*String} sender * @param {*String} receiver * @param {*Object} message * @param {*Boolean} isOnline */ const sendMessageToClient = function (sender, receiver, message) { let messageInfo = { sender: sender, message: message, msgType: "message", timestamp: getTime(), userList: userList } //若是接收方在線,則給其發送 if (receiver) { messageInfo.receiver = receiver clients[receiver].send(JSONToString(messageInfo)) } clients[sender].send(JSONToString(messageInfo)) console.log('向客戶端發送消息', JSONToString(messageInfo)) } 複製代碼
/** * * @param {*String} currentUser * @param {*Boolean} isOnline */ const noticeOnlineOrOffLine = function (currentUser, isOnline) { for (var key in clients) { //上/下線須要更新其餘用戶的好友列表 let noticeUserMessage = {} let exceptCurrentUserList = userList.filter(el => el != currentUser) noticeUserMessage = Object.assign(onlineOrOffLineNoticeMsg(key, isOnline), { userList: isOnline ? userList : exceptCurrentUserList }) let isOnlineMsg = isOnline ? '上線' : '下線' console.log('用戶:' + currentUser + isOnlineMsg + ',消息:' + JSONToString(noticeUserMessage)) clients[key].send(JSONToString(noticeUserMessage)) } if (!isOnline) { delete clients[currentUser]; } } 複製代碼
//上下線消息模板 const onlineOrOffLineNoticeMsg = function (receiver, isOnline) { return { receiver: receiver, msgType: 'notice', message: isOnline ? receiver + '上線了' : receiver + '下線了', timestamp: getTime() } } 複製代碼
至此,這個小應用的主體功能基本完善了,萬里長征第一步,哈哈😁,因爲目前只是爲了把聊天的流程走通,連界面都是隨便寫了幾個div(又不是不能用,手動狗頭),可能各位客官已經發現了,mongo
尚未運用到聊天過程當中🤣,由於目前對mongo
的啓用姿式還不夠深刻,生怕給本身挖坑,等進一步規劃好再搞數據吧。
後期還須要完善的功能主要是羣聊(選擇固定用戶的那種,不是全部人的聊天室),其次就是朋友圈功能的實現,涉及數據存儲,圖文處理等等的內容,還須要規劃和打磨一下,還會進一步更新。
最後,因爲本人水平有限,尚且可能運用了比較很差的業務實現方式,但願沒給初學者形成誤導,也請各路大神進行指正、建議和交流。
附github地址tiny-chat