是時候用Node.js搞一波事情了,基於Node.js的聊天小應用

最近想作一個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

前端Websocket通訊實現

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))
}
複製代碼

搭建express服務

目錄結構

---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)
    })
})
複製代碼

express 解析json格式的請求參數須要的配置

//app.js
app.use(express.json()) 
app.use(express.urlencoded({
    extended: true
})) 
複製代碼

添加路由

//app.js
const userRouter = require('./routes/user')
app.use('/', userRouter)
複製代碼

mongo基礎配置

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
複製代碼

Websocket server 基本實現

步驟1.開啓服務

const webSocket = require('ws'); //引入ws服務器模塊
const ws = new webSocket.Server({
    port: 8000
}); //建立服務器,端口爲8000

const {
    JSONToString,
    getTime
} = require('../common/function')
var clients = {}  //記錄當前在線用戶信息
var userList = [] //僅存儲當前在線用戶名
複製代碼

步驟2. 鏈接服務,與client交互

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)
    })
})
複製代碼

步驟3.給指定用戶發送消息

/** * * @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))
}
複製代碼

步驟4.通知其餘用戶當前用戶的在線狀態

/** * * @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

相關文章
相關標籤/搜索