JavaScript與Node.js一起打造一款聊天App
聊天是我們?nèi)伺c人交流最直接的方式,互聯(lián)網(wǎng)的加入使我們交流更加便捷。我們手機(jī)上的微信、QQ是我們手機(jī)必不可少的應(yīng)用軟件。那么,我們是否可以做一款聊天應(yīng)用呢?
之前我自己閑著沒(méi)事,研究過(guò)一些技術(shù),做了一款即時(shí)通訊應(yīng)用,下面我將選取幾幅具有代表性的圖片供大家參考。
一、應(yīng)用示圖
以上是這款應(yīng)用的主要頁(yè)面,功能可能相對(duì)簡(jiǎn)陋點(diǎn),不過(guò)基本的功能已經(jīng)實(shí)現(xiàn)了,下面我將給出核心代碼,全部源碼地址在文末。
二、部分核心源碼
前臺(tái)主要核心邏輯:
這里我只列舉了js核心代碼,查看完整代碼可以去文末。
- function sock() {
- return io.connect("http://localhost:3003"); // http環(huán)境下
- }
- // 心跳機(jī)制
- document.addEventListener('visibilitychange', function () {
- if (document.visibilityState == 'hidden') {
- //記錄頁(yè)面隱藏時(shí)間
- sock()
- console.log('隱藏了')
- }
- })
- setInterval(() => {
- sock()
- }, 10000);
- var socket = sock()
- var re = document.querySelector("#re");
- var register1 = document.querySelector(".register");
- var init = document.querySelector(".init");
- var passr = document.querySelector("#passr");
- var passl = document.querySelector("#passl");
- var login1 = document.querySelector(".login");
- var register_b = document.querySelector("#register_b");
- var lo = document.querySelector("#lo");
- var chat = document.querySelector("#chat");
- var login_b = document.querySelector("#login_b");
- var myMes = "";
- var vf = "";
- var na = "";
- var p = "";
- var we = "";
- var div = "";
- var v = "";
- var q = 0;
- var regCn = /[@:]/im;
- var pattern = /^[\u4E00-\u9FA5]{1,5}$/;
- // 同意
- document.querySelector('.yes').onclick=function () {
- document.querySelector('.dark').style.display='none'
- }
- document.querySelector('.ys').onclick = function () {
- document.querySelector('.dark').style.display = 'block'
- }
- // 初始頁(yè)面注冊(cè)
- document.querySelector("#reg").onclick = function () {
- register1.style.display = "block";
- init.style.display = "none";
- document.querySelector(".bg").style.display = "none";
- }
- // 初始頁(yè)面登錄
- document.querySelector("#log").onclick = function () {
- login1.style.display = "block";
- init.style.display = "none";
- document.querySelector(".bg").style.display = "none";
- }
- // 登錄按鈕
- login_b.onclick = function () {
- login();
- }
- // 注冊(cè)按鈕
- register_b.onclick = function () {
- register();
- }
- //發(fā)送
- document.getElementById("btn").onclick = function () {
- send();
- };
- // 內(nèi)容填充
- document.getElementById("text").onkeyup = function () {
- if (document.getElementById("text").value.length != 0) {
- document.getElementById("btn").style.cssText = "background:#98E165;color:#fff;"
- } else {
- document.getElementById("btn").style.cssText = "background: #DDDEE2;color:#fff"
- }
- }
- document.querySelector("#text").onclick = function () {
- document.querySelector('#text').scrollIntoView(false);
- }
- // 傳名
- var users2 = "";
- socket.on('users', function (users) {
- users2 = users;
- // console.log(users2);
- });
- // 傳密碼
- var pass2 = ""
- socket.on('pass', function (val) {
- pass2 = val;
- // console.log(pass2)
- });
- // 統(tǒng)計(jì)在線人數(shù)
- var arrh = []
- socket.on('dataval', function (val) {
- vf = val;
- console.log(vf);
- for (let i = 0; i < vf.length; i++) {
- // uu++
- arrh.push(vf[i])
- console.log(arrh)
- }
- var rf = [...new Set(arrh)]
- console.log(rf)
- rf = vf
- for (let j = 0; j < rf.length; j++) {
- var li = document.createElement("li");
- li.classList.add("active");
- li.innerText = rf[j]
- console.log(rf[j])
- socket.emit("time", rf[j]);
- document.querySelector(".fix").appendChild(li);
- }
- });
- socket.on('join', function (val) {
- document.querySelector(".fix").innerHTML = ''
- })
- socket.on('disconnect', function (val) {
- console.log('離開(kāi)了')
- document.querySelector(".fix").innerHTML = ''
- })
- // 生成數(shù)組
- var ar = "";
- socket.on('array', function (val) {
- ar = val;
- // console.log(ar);
- });
- // 封裝注冊(cè)
- function register() {
- if (re.value.length == 0) {
- sweetAlert("請(qǐng)輸入用戶名!");
- return false;
- } else if (regCn.test(re.value)) {
- sweetAlert("格式錯(cuò)誤,不能夠用和:符號(hào)取名,請(qǐng)重新輸入!");
- return false;
- } else if (pattern.test(re.value)) {
- sweetAlert("不能使用中文字符哦!");
- return false;
- } else if (!(re.value.length == 0 && regCn.test(re.value))) {
- if (users2.indexOf(re.value) != -1) {
- sweetAlert("已經(jīng)注冊(cè)啦,換一個(gè)用戶名吧!");
- } else {
- names(re.value.trim());
- pass(passr.value.trim());
- sweetAlert("注冊(cè)成功,您的用戶名:" + re.value.trim());
- document.querySelector(".swal-button").onclick = function () {
- window.location.reload();
- }
- }
- }
- }
- //移動(dòng)端使用touchend
- var event = navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i) ? 'touchend' : 'click';
- // 選擇器
- var Q = function (id) {
- return document.getElementById(id)
- };
- //右
- var _right = new mSlider({
- dom: ".layer-right",
- direction: "right"
- });
- Q("btnRight").addEventListener(event, function (e) {
- _right.open();
- })
- // 封裝登錄
- function login() {
- if (lo.value.length == 0) {
- sweetAlert("請(qǐng)輸入用戶名!");
- return false;
- } else if (regCn.test(lo.value)) {
- sweetAlert("格式錯(cuò)誤,不能夠用和:符號(hào)取名,請(qǐng)重新輸入!");
- return false;
- } else if (pattern.test(lo.value)) {
- sweetAlert("不能使用中文字符哦!");
- return false;
- } else if (!(lo.value.length == 0 && regCn.test(lo.value))) {
- if (users2.indexOf(lo.value) != -1) {
- for (var i = 0; i < users2.length; i++) {
- if (users2[i] === lo.value && pass2[i] === passl.value) {
- if (ar.indexOf(lo.value) == -1) {
- sweetAlert("恭喜您,登錄成功!");
- socket.emit('setName', lo.value.trim());
- names1(lo.value.trim());
- login1.style.display = "none";
- document.querySelector(".bg").style.display = "none";
- document.querySelector(".cd span").style.display = "none";
- document.querySelector(".title img").style.display = "block";
- document.querySelector(".fix").style.display = "block";
- document.querySelector(".title").style.display = "block";
- _right.open();
- document.querySelector(".swal-button").onclick = function () {
- document.getElementById("text").focus();
- document.querySelector(".fix").addEventListener('click', function (e) {
- if (e.target.nodeName === "LI" && e.target.innerText != document.title) {
- _right.close();
- document.querySelector(".chat_b").style.display = "block";
- document.querySelector(".box").style.display = "block";
- document.querySelector(".tit").innerText = e.target.innerText;
- document.querySelector(".ys").style.display="none";
- document.querySelector("#text").focus();
- onOff = true;
- } else {
- sweetAlert("不能跟自己聊天哦~");
- }
- })
- }
- } else {
- sweetAlert("不能重復(fù)登錄哦!");
- return
- }
- }
- if (users2[i] === lo.value && pass2[i] != passl.value) {
- sweetAlert("密碼錯(cuò)誤!");
- return;
- }
- }
- } else {
- sweetAlert("請(qǐng)先注冊(cè)哦!");
- login1.style.display = "none";
- register1.style.display = "block";
- }
- }
- }
- // 傳名
- function names(value) {
- this.name = value;
- socket.emit("reg", name);
- }
- function names1(value) {
- this.name1 = value;
- socket.emit("join", name1);
- document.title = name1
- }
- // 傳密碼
- function pass(value) {
- socket.emit("pass", value);
- }
- socket.on("join", function (user) {
- this.na = user;
- })
- socket.on("reg", function (user) {
- this.na1 = user;
- })
- // 私發(fā)消息
- socket.on('message1', function (data) {
- var p1 = document.createElement("div");
- var s1 = document.createElement("p");
- var s2 = document.createElement("p");
- var div1 = document.createElement("div");
- var em = document.createElement("em");
- var ads = document.createElement("audio");
- ads.src = "https://www.maomin.club/data/res.mp3";
- ads.className = "ads";
- s1.className = "chatlist";
- s2.className = "chatlist1";
- em.className = "zwasked1";
- div1.className = "divbox";
- s1.innerText = data.from;
- s2.innerText = data.msg;
- s1.appendChild(em);
- p1.appendChild(s1);
- p1.appendChild(s2);
- chat.appendChild(ads);
- ads.play();
- div1.appendChild(p1);
- chat.appendChild(div1);
- chat.scrollTop = chat.scrollHeight;
- });
- // 私聊發(fā)送
- function send() {
- if (document.getElementById("text").value != "") {
- socket.emit('sayTo', {
- from: lo.value,
- to: document.querySelector(".tit").innerText,
- msg: document.querySelector("#text").value,
- })
- var p1 = document.createElement("div");
- var s1 = document.createElement("p");
- var s2 = document.createElement("p");
- var em = document.createElement("em");
- var div1 = document.createElement("div");
- var ads = document.createElement("audio");
- p1.style.cssText = "float:right;";
- s2.style.cssText = "color:#333;"
- ads.src = "https://www.maomin.club/data/s.wav";
- ads.className = "ads";
- div1.className = "divbox";
- s1.className = "chatlist";
- s1.style.cssText = "color:#333 !important;float:right; !important";
- s2.className = "chatlist2";
- em.className = "zwasked";
- s1.innerText = lo.value;
- s2.innerText = document.querySelector("#text").value;
- s1.appendChild(em);
- p1.appendChild(s1);
- p1.appendChild(s2);
- chat.appendChild(ads);
- ads.play();
- div1.appendChild(p1);
- chat.appendChild(div1);
- chat.scrollTop = chat.scrollHeight;
- } else {
- sweetAlert('請(qǐng)輸入內(nèi)容!');
- }
- chat.scrollTop = chat.scrollHeight;
- document.querySelector("#text").value = "";
- document.querySelector("#text").focus();
- }
后臺(tái)主要核心邏輯:
我這里只列舉了http環(huán)境的,完整代碼中有https環(huán)境的。
- var http=require("http");
- var fs=require("fs");
- var express = require('express');
- var ws=require("socket.io");
- var path=require("path");
- var _ = require('underscore');
- var usocket = [];
- var usocket1 = [];
- var pass=[];
- var data=[];
- var hashName = {};
- var onlineCount = 0;
- var app = express();
- // 靜態(tài)文件識(shí)別
- app.use(express.static(path.join(__dirname, './public')));
- var server=http.createServer(function (req,res) {
- var filename = req.url.split('/')[req.url.split('/').length-1];
- var suffix = req.url.split('.')[req.url.split('.').length-1];
- if(req.url==='/'){
- res.writeHead(200, {'Content-Type': 'text/html'});
- var html = fs.readFileSync("./public/index.html");
- res.end(html)
- }else if(suffix==='css'){
- res.writeHead(200, {'Content-Type': 'text/css'});
- res.end(get_file_content(path.join(__dirname, 'public', 'css', filename)));
- }else if(suffix==='js') {
- res.writeHead(200, {'Content-Type': 'text/javascript'});
- res.end(get_file_content(path.join(__dirname, 'public', 'js', filename)));
- }else if (suffix in ['gif', 'jpeg', 'jpg', 'png']) {
- res.writeHead(200, {
- 'Content-Type': 'image/' + suffix
- });
- res.end(get_file_content(path.join(__dirname, 'public', 'images', filename)));
- }
- });
- function get_file_content(filepath) {
- return fs.readFileSync(filepath);
- }
- // 獲取在線
- function broadcast() {
- io.sockets.emit("dataval", hashName);
- }
- //提供私有socket
- function privateSocket(toId) {
- return (_.findWhere(io.sockets.sockets, {
- id: toId
- }));
- }
- // 封裝刪除
- function removeByValue(arr, val) {
- for (var i = 0; i < arr.length; i++) {
- if (arr[i] == val) {
- arr.splice(i, 1);
- break;
- }
- }
- }
- // 連接socket
- var io=ws(server);
- io.on("connection",function(socket){
- // 寫(xiě)入成功后讀取測(cè)試
- fs.readFile('./user.xls', 'utf-8', function (err, data) {
- if(data!=null){
- var value = data.split('\n');
- io.sockets.emit("users", value);
- }
- });
- // 寫(xiě)入成功后讀取測(cè)試
- fs.readFile('./password.xls', 'utf-8', function (err,data) {
- if(data!=null){
- var pass1=data.split('\n');
- io.sockets.emit("pass", pass1);
- }
- });
- broadcast();
- // 生成名字
- socket.on('setName', function (data) {
- var name = data;
- hashName[name] = socket.id;
- // console.log(hashName[name]);
- broadcast();
- });
- // 私聊發(fā)送
- socket.on('sayTo', function (data) {
- var toName = data.to;
- var toId;
- console.log(toName);
- if (toId = hashName[toName]) {
- privateSocket(toId).emit('message1', data);
- }
- });
- // 離開(kāi)
- socket.on('disconnect', function (name) {
- name=this.i2;
- io.emit("disconnect", name);
- removeByValue(data, name);
- io.sockets.emit("dataval", data);
- })
- // 在線
- socket.on('time', function (val) {
- // console.log(val);
- })
- // 注冊(cè)
- socket.on("reg", function (name) {
- usocket[name] = socket;
- this.i1=name;
- io.emit("reg", name);
- var myname =this.i1+"\n";
- fs.writeFile('./user.xls', myname, {
- 'flag': 'a'
- }, function (err) {
- if (err) {
- throw err;
- }
- // 寫(xiě)入成功后讀取測(cè)試
- fs.readFile('./user.xls', 'utf-8', function (err,data) {
- if (err) {
- throw err;
- }
- });
- });
- })
- // 加入
- io.emit('connected', ++onlineCount);
- // console.log(data);
- io.sockets.emit("array", data);
- socket.on("join", function (name) {
- usocket1[name] = socket;
- this.i2 = name;
- io.emit("join", name);
- data.push(name);
- io.sockets.emit("dataval", data);
- })
- // 密碼
- socket.on("pass",function(val){
- pass[val]=socket;
- this.i2=val;
- io.emit("pass", val);
- var password=this.i2+"\n";
- fs.writeFile('./password.xls', password, {
- 'flag': 'a'
- }, function (err) {
- if (err) {
- throw err;
- }
- });
- })
- });
- server.listen(3003);
- console.log("服務(wù)器運(yùn)行中");
三、源碼地址
這個(gè)項(xiàng)目是之前寫(xiě)的,歡迎大家進(jìn)行指正。大家可以復(fù)制下面的源碼地址,拉取下來(lái)就可以在本地實(shí)現(xiàn)一個(gè)聊天服務(wù)。如果你有服務(wù)器可以把它部署在服務(wù)器上,這樣你就可以有一個(gè)屬于自己的聊天App了。大家可以根據(jù)源碼進(jìn)行學(xué)習(xí),有不明白的可以隨時(shí)問(wèn)我。
https://github.com/maomincoding/chat3