先言

这我以前写的,这里就是单纯分享下代码,不算正经文章。效果如下,前端用一个单html文件。然后后端用node.js和socket.io,也是只用一个单js文件就好。这里可以看下代码的实现逻辑就好,因为来连数据库才能运行的。有需要的话告诉我,我也可以把存取数据库代码逻辑和接口逻辑删了,改成时时存时时失效的,这样就能直接打开html文件直接随意运行了。

简易聊天室Java_前端

简易聊天室Java_开发语言_02

代码

前端
html:

<!DOCTYPE html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <link rel="stylesheet" href="./chat.css" />
  </head>

  <body>
    <img class="bg" src="../bg.webp" alt="" />
    <div class="chat">
      <div class="left">
        <div class="title">
          <h3>聊天室</h3>
        </div>
        <div class="content" id="content">
          <div class="history" id="history">
            <button>查看最近的聊天</button>
          </div>
          <ul id="messages">
            <!--             <li class="data">
              <div class="img">
                <img
                  src="https://bbs-img.huaweicloud.com/user/img/head/1617968414164_1201_6320.jpg"
                  alt=""
                />
              </div>
              <p><span>小杰(老师):</span> 【摘要】</p>
            </li>
            <li class="data2">
              <p>
                <span>我:</span> 【摘要】
                首先,强类型不允许随意的隐式类型转换,而弱类型是允许的。JavaScript就是经典的弱类型语言。而Typescript可以说是JavaScript的超集,在js的基础上新增了许多语法特性,使得类型不再可以随意转换,能大大减少开发阶段的错误。
              </p>
              <div class="img">
                <img
                  src="https://bbs-img.huaweicloud.com/user/img/head/1617968414164_1201_6320.jpg"
                  alt=""
                />
              </div>
            </li> -->
          </ul>
        </div>
        <div class="shuru">
          <textarea
            id="m"
            type="text"
            autocomplete="off"
            autofocus=""
          ></textarea>
          <button id="btn">
            发送
          </button>
          <div class="emoji" id="emoji">
            😊
            <div id="ej" style="display: none;">
              <span>😂</span>
              <span>😐</span>
              <span>😤</span>
              <span>😴</span>
              <span>😫</span>
              <span>😧</span>
              <span>🤪</span>
              <span>😛</span>
              <span>😭</span>
              <span>😡</span>
              <span>👹</span>
              <span>🤢</span>
              <span>❤️</span>
              <span>💔</span>
              <span>👄</span>
              <span>🧝</span>
              <span>💇♀️</span>
              <span>💘</span>
              <span>👻</span>
              <span>☠️</span>
              <span>😱</span>
              <span>🌬️</span>
              <span>👌</span>
              <span>🤝</span>
            </div>
          </div>
        </div>
      </div>
      <div class="right">
        <div class="title">
          <h3>当前在线成员<span id="people">(0)</span></h3>
        </div>
        <div class="list" id="list">
          <!--      <div class="item">
            <img
              src="https://bbs-img.huaweicloud.com/user/img/head/1617968414164_1201_6320.jpg"
              alt=""
            />
            <p>小杰(学生)33333330000</p>
          </div> -->
        </div>
      </div>
    </div>
  </body>
  <!-- <script src="https://unpkg.com/axios/dist/axios.min.js"></script> -->
  <script>
    // 封装的ajax请求
    function ajax(url, params) {
      return new Promise((resolve, reject) => {
        //要发个get请求知道是你来了,后端解析你的信息

        let xhr = new XMLHttpRequest();

        // 设置属性
        xhr.open("post", url);

        // 如果想要使用post提交数据,必须添加此行
        xhr.setRequestHeader(
          "Content-type",
          "application/x-www-form-urlencoded"
        );
        // 将数据通过send方法传递
        xhr.send(params);
        // 发送并接受返回值
        xhr.onreadystatechange = function() {
          // 这步为判断服务器是否正确响应
          if (xhr.readyState == 4 && xhr.status == 200) {
            resolve(xhr.responseText);
          }
        };
      });
    }
  </script>
  <script src="./static/dist/socket.io.js"></script>
  <script>
    //要发个请求知道是你来了,后端解析你的信息
    /*    axios
      .post("http://localhost:8849/", {
        username: "787",
      })
      .then(function(response) {
        console.log(response);
      }); */

    let net_name = window.sessionStorage.getItem("net_name");
    let icon = window.sessionStorage.getItem("icon");
    let role = window.sessionStorage.getItem("role");
    let uid = window.sessionStorage.getItem("uid");
    if (role == "1") {
      role = "学生";
    } else if (role == "2") {
      role = "老师";
    } else {
      role = "管理员";
    }
    /*     let uid = Number(
      Math.random()
        .toString()
        .substr(3, 10) + Date.now()
    ).toString(36); */
    ajax(
      "http://localhost:8849/",
      `net_name=${net_name}&icon=${icon}&role=${role}&uid=${uid}`
    ).then(function(response) {
      console.log(response);
    });

    // 做个判断

    var socket = io("http://localhost:8849/");
    var btn = document.getElementById("btn");
    var ul = document.getElementById("messages");
    let cxt = document.getElementById("m");
    let people = document.getElementById("people");
    let content = document.getElementById("content");
    let list = document.getElementById("list");
    let history = document.getElementById("history");
    let emoji = document.getElementById("emoji");
    let ej = document.getElementById("ej");

    //选表情
    emoji.onclick = function(e) {
      if (ej.style.display == "block") {
        ej.style.display = "none";
      } else {
        ej.style.display = "block";
      }
    };
    ej.onclick = function(e) {
      // console.log(e.target.innerText);
      if (e.target.nodeName.toLowerCase() == "span") {
        cxt.value += e.target.innerText;
      }
    };

    history.onclick = function() {
      //查看历史记录
      history.style.display = "none";
      ajax("http://localhost:8849/query", "").then(function(response) {
        let data = JSON.parse(response).data;
        //先看下有几个小li了
        lis = document.getElementById("messages").getElementsByTagName("li")
          .length;
        console.log(lis);
        for (let i = lis; i < data.length - 1; i++) {
          let newli = document.createElement("li");
          if (data[i].uid == uid) {
            newli.setAttribute("class", "data2");
            newli.innerHTML = `
             <p>
                <span>${data[i].net_name}(${data[i].role}):</span> ${data[i].content}
              </p>
           <div class="img">
                <img
                  src="http://localhost:8848/${data[i].icon}"
                  alt=""
                />
              </div>
             `;
          } else {
            newli.setAttribute("class", "data");
            newli.innerHTML = `
           <div class="img">
                <img
                  src="http://localhost:8848/${data[i].icon}"
                  alt=""
                />
              </div>
              <p>
                <span>${data[i].net_name}(${data[i].role}):</span> ${data[i].content}
              </p>`;
          }
          ul.insertBefore(newli, ul.childNodes[0]);
        }
        //滚动到底部
        content.scrollTo({
          top: content.scrollHeight,
          behavior: "smooth",
        });
      });
    };

    // 点击send按钮,把消息发送给服务器
    btn.onclick = function() {
      if (cxt.value == "") return;
      // 把登录的用户名和输入框内容全部发送给服务器,让服务器做一次广播,才能同步用户信息。
      //把自己名字传给服务器,其实也不用,我在那局部变量保存了
      socket.emit("message", { net_name, role, uid, icon, inpval: cxt.value });

      //数据库添加数据
      ajax(
        "http://localhost:8849/add",
        `net_name=${net_name}&icon=${icon}&role=${role}&content=${cxt.value}&uid=${uid}`
      ).then(function(response) {
        console.log(response);
      });
      return false;
    };
    //监听服务器的广播消息,同步用户信息,msg就是点击发送按钮发送的用户信息
    socket.on("message", function(msg) {
      //在这可以通过名字判断是自己就放右边,别人就放左边

      // 每个客户端将用户的消息渲染
      var newli = document.createElement("li");
      // newli.innerHTML = msg.net_name + "(" + msg.role + ")" + ":" + msg.inpval;
      if (msg.uid == uid) {
        newli.setAttribute("class", "data2");
        newli.innerHTML = `
              <p>
                <span>${msg.net_name}(${msg.role}):</span> ${msg.inpval}
              </p>
               <div class="img">
                <img
                  src="http://localhost:8848/${msg.icon}"
                  alt=""
                />
              </div>`;
      } else {
        newli.setAttribute("class", "data");
        newli.innerHTML = `
           <div class="img">
                <img
                  src="http://localhost:8848/${msg.icon}"
                  alt=""
                />
              </div>
              <p>
                <span>${msg.net_name}(${msg.role}):</span> ${msg.inpval}
              </p>`;
      }
      ul.appendChild(newli);
      //滚动到底部
      content.scrollTo({
        top: content.scrollHeight,
        behavior: "smooth",
      });

      cxt.value = "";
    });
    // 服务器端监听服务端建立连接发来的信息,用于渲染温馨提示信息,msg是服务器返回广播的用户对象数据
    socket.on("loginin", function(msg) {
      // 生成用户进入房间提示信息标签
      let tip = document.createElement("p");
      console.log(msg.userList);
      tip.innerHTML = msg.des;
      // 设置样式
      tip.className = "tips";
      ul.appendChild(tip);
      // people是显示当前聊天室人数
      people.innerHTML = "( " + msg.count + "人 )";
      //更新在线用户列表
      list.innerHTML = " ";
      for (let i = 0; i < msg.userList.length; i++) {
        list.innerHTML += `
          <div class="item">
            <img
              src="http://localhost:8848/${msg.userList[i].icon}"
              alt=""
            />
            <p>${msg.userList[i].net_name}(${msg.userList[i].role})</p>
          </div>
         `;
      }
    });
    //服务器端监听服务端建立连接发来的信息,msg是服务器返回广播的用户对象数据
    //关掉浏览器会执行下面这个函数
    socket.on("loginout", function(msg) {
      // 生成用户退出提示信息
      let tip = document.createElement("p");
      console.log(msg.userList);
      tip.innerHTML = msg.des;
      tip.className = "tips";
      ul.appendChild(tip);
      people.innerHTML = "( " + msg.count + "人 )";
      //更新在线用户列表
      list.innerHTML = " ";
      for (let i = 0; i < msg.userList.length; i++) {
        list.innerHTML += `
          <div class="item">
            <img
              src="http://localhost:8848/${msg.userList[i].icon}"
              alt=""
            />
            <p>${msg.userList[i].net_name}(${msg.userList[i].role})</p>
          </div>
         `;
      }
    });
  </script>
</html>

css:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  height: 600px;
  display: flex;
  justify-content: center;
}
.bg {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  object-fit: cover;
  z-index: -1000;
}
.history {
  width: 100%;
  text-align: center;
}
.history button {
  border: none;
  height: 30px;
  width: 120px;
  cursor: pointer;
  background-color: rgb(88, 180, 230);
  border-radius: 2px;
  color: white;
}
.history button:hover {
  background-color: rgb(125, 195, 236);
}
.history button:active {
  background-color: rgb(88, 180, 230);
}
.chat {
  width: 1100px;
  height: 596px;
  margin: 20px auto;
}
.left {
  width: 800px;
  height: 100%;
  float: left;
  background-color: rgba(223, 241, 251, 0.8);
  border-radius: 10px;
  box-shadow: 0 0 5px rgb(153, 153, 153);
}
.left .emoji {
  position: absolute;
  width: 30px;
  height: 30px;
  background-color: rgb(88, 180, 230);
  right: 90px;
  line-height: 30px;
  text-align: center;
  border-radius: 2px;
  cursor: pointer;
}
.left .emoji div {
  width: 200px;
  height: 100px;
  background-color: #fff;
  position: absolute;
  top: -110px;
  left: -50%;
  display: flex;
  justify-content: space-evenly;
  flex-wrap: wrap;
  border-radius: 3px;
  align-items: center;
}
.left .emoji div::after {
  content: "";
  position: absolute;
  bottom: -9px;
  left: 25px;
  width: 10px;
  height: 10px;
  background-color: rgb(255, 255, 255);
  -webkit-clip-path: polygon(53% 100%, 0 0, 100% 0);
  clip-path: polygon(53% 100%, 0 0, 100% 0);
}
.left .emoji div span {
  line-height: 25px;
  text-align: center;
  font-size: 15px;
  width: 25px;
  height: 25px;
}
.right {
  width: 290px;
  float: right;
  height: 100%;
  border-radius: 10px;
  background-color: rgba(223, 241, 251, 0.8);
  box-shadow: 0 0 5px rgb(153, 153, 153);
}
.right .list {
  padding: 10px;
  width: 100%;
  height: 520px;
  display: flex;
  flex-wrap: wrap;
  align-content: start;
  overflow: auto;
}
.right .list .item {
  margin-top: 6px;
  width: 100%;
  height: 50px;
  background-color: red;
}
.right .list .item img {
  width: 50px;
  height: 50px;
  object-fit: cover;
  float: left;
}
.right .list .item p {
  line-height: 50px;
  background-color: #fff;
  text-indent: 1em;
  font-family: "fangsong";
}
.title {
  border-radius: 10px 10px 0 0;
  line-height: 60px;
  color: black;
  border-bottom: 1px solid #999;
  text-align: center;
  background-color: rgb(128, 199, 237);
}

.content {
  padding: 10px;
  width: 100%;
  overflow: auto;
  height: 400px;
}

#messages li {
  list-style: none;
  padding: 5px;
}

.shuru {
  border-top: solid 2px rgb(143, 143, 143);
  height: 130px;
  position: relative;
}
#m {
  width: 100%;
  height: 83px;
  outline: none;
  color: #666;
  overflow: auto;
  font-size: 16px;
  line-height: 25px;
  padding: 5px 10px;
  border: none;
  background-color: transparent;
  text-indent: 0;
}

#btn {
  border: none;
  height: 30px;
  width: 80px;
  cursor: pointer;
  position: absolute;
  right: 6px;
  bottom: 10px;
  background-color: rgb(88, 180, 230);
  border-radius: 2px;
}
#btn:hover {
  background-color: rgb(125, 195, 236);
}
#btn:active {
  background-color: rgb(88, 180, 230);
}
.tips {
  width: 50%;
  margin: 4px auto;
  padding: 2px 5px;
  text-align: center;
  font-size: 8px;
  border-radius: 10px;
  background-color: #cfcfcf;
  color: #fff;
}

.title #people {
  font-size: 8px;
  color: rgb(250, 27, 27);
}

.data {
  width: 100%;
  display: flex;
  justify-content: flex-start;
  min-height: 60px;
}
.data .img {
  width: 60px;
}
.data img {
  flex: 1;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 3px rgb(153, 153, 153);
}
.data p {
  font-size: 14px;
  line-height: 20px;
  margin-left: 10px;
  background-color: rgb(18, 183, 245);
  color: white;
  border-radius: 5px;
  padding: 10px;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.data p span {
  width: 120px;
  overflow: hidden;
  color: rgb(255, 123, 0);
  background-color: rgb(223, 241, 251);
  border-radius: 2px;
  padding: 0 5px;
  font-size: 14px;
  margin-bottom: 5px;
}
.data2 p span {
  width: 120px;
  overflow: hidden;
  color: rgb(255, 123, 0);
  background-color: rgb(223, 241, 251);
  border-radius: 2px;
  padding: 0 5px;
  font-size: 14px;
  margin-bottom: 5px;
}
.data p::after {
  position: absolute;
  content: "";
  top: 10px;
  left: -9px;
  width: 10px;
  height: 10px;
  background-color: rgb(18, 183, 245);
  -webkit-clip-path: polygon(100% 100%, 100% 0, 0 51%);
  clip-path: polygon(100% 100%, 100% 0, 0 51%);
}
/*  */
.data2 {
  width: 100%;
  display: flex;
  justify-content: flex-end;
  min-height: 60px;
}
.data2 .img {
  width: 60px;
}
.data2 img {
  flex: 1;
  width: 50px;
  height: 50px;
  border-radius: 50%;
  object-fit: cover;
  box-shadow: 0 0 3px rgb(153, 153, 153);
}
.data2 p {
  line-height: 20px;
  margin-right: 10px;
  font-size: 14px;
  background-color: rgb(18, 183, 245);
  color: white;
  border-radius: 5px;
  padding: 10px;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.data2 p::after {
  position: absolute;
  content: "";
  top: 10px;
  right: -9px;
  width: 10px;
  height: 10px;
  background-color: rgb(18, 183, 245);
  -webkit-clip-path: polygon(100% 50%, 0 0, 0 100%);
  clip-path: polygon(100% 50%, 0 0, 0 100%);
}

后端js文件:

/*
 * @Author: yournet_name
 * @Date: 2022-03-10 21:39:37
 * @LastEditTime: 2022-03-13 11:50:33
 * @LastEditors: Please set LastEditors
 * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 * @FilePath: \onlinechat-main\app.js
 */
const express = require('express')
const app = express()
var http = require('http').Server(app)
var io = require('socket.io')(http, { cors: true })



// 使得能接收post请求参数
const bodyParser = require('body-parser')
app.use(bodyParser.urlencoded({ extended:false }))
//使得express接收json格式数据
app.use(express.json());
// 解决跨域 
const cors = require('cors')
app.use(cors({credentials:true,origin:true}))
 // knex插件,连接数据库
const knex = require('knex')({
    client: 'mysql',
    connection: {
        hhost     : 'localhost',    //   
        user     : '',
        password : '',
        database : ''
    }
})


//时间格式转换
Date.prototype.Format = function (fmt) { // author: meizz
    var o = {
        "M+": this.getMonth() + 1, // 月份
        "d+": this.getDate(), // 日
        "h+": this.getHours(), // 小时
        "m+": this.getMinutes(), // 分
        "s+": this.getSeconds(), // 秒
        "q+": Math.floor((this.getMonth() + 3) / 3), // 季度
        "S": this.getMilliseconds() // 毫秒
    };
    if (/(y+)/.test(fmt))
        fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
    for (var k in o)
        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            return fmt;
} 

/* / 使用
var date = new Date();
date.format("yyyy-MM-dd"); */








/* app.all('*', function(req, res, next) {  
  res.header("Access-Control-Allow-Origin", "*");  
  res.header("Access-Control-Allow-Headers", "X-Requested-With");  
  res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");  
  res.header("X-Powered-By",' 3.2.1')  
  res.header("Content-Type", "application/json;charset=utf-8");  
  next();  
});  */ 


//meoji表情转义
function utf16toEntities(str) {
    var patt=/[\ud800-\udbff][\udc00-\udfff]/g; // 检测utf16字符正则
    str = str.replace(patt, function(char){
            var H, L, code;
            if (char.length===2) {
                H = char.charCodeAt(0); // 取出高位
                L = char.charCodeAt(1); // 取出低位
                code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00; // 转换算法
                return "&#" + code + ";";
            } else {
                return char;
            }
        });
    return str;
}




var userList = [] //当前在线用户数组
var user = {
  net_name:"",
  role:"",
  uid:"",
  icon:""
} //新进入用户 , 要公告推送消息

var count = 0

//通用接口,通过接口获取是谁进来了 ,要公告推送消息
app.post('/',(req,res)=>{

  let {net_name,role,uid,icon} = req.body; 
  user.net_name = net_name;
  user.role = role;
  user.uid = uid;
  user.icon = icon;
  userList.push({
      net_name,
      role,
      uid,
      icon
  })

  // 保存用户的名称
  
  // 返回状态码,通过状态码执行客户端页面跳转
  res.send({state:net_name})
  // res.sendFile(__dirname + '/index.html')
})
//新增聊天数据 用户点击发送应该也触发这个接口  到达50条后应该删除以前的
app.post('/add',(req,res)=>{
        
        // req.file得到文件信息,req.body的到文件文本信息
       let {net_name,icon,role,content,uid} = req.body  
        content = utf16toEntities(content)
        let create_time = new Date();
        create_time.Format("yyyy-MM-dd-hh:mm");  

           knex('chat').insert({net_name,icon,role,create_time,content,uid}).then(value=>{
             //   res.send({msg:'新增成功'})
            }).catch(value=>{
                    res.send({code:'400',msg:'发生错误'})  
                    console.log(value);
                })   
    }) 
//查询历史聊天记录   直接只取20条把
app.post('/query',(req,res)=>{
       knex.schema.raw(`select * from chat order by id  desc  limit 20`).then(value=>{
            if(res.length === 0){
              res.send({code:200,msg:'无结果'})
            }else{
              res.send({code:200,msg:'查询成功',data:value[0]})
            }
        }).catch(value=>{
            console.log(value);
        })
    })






//入口函数,连接进程 每一个用户(连接状态)都有一个下面这个函数,独立的
io.on('connection', function (socket) {


/*     let userSelf = {
      net_name:"",
      role:"",
      uid:""};
    userSelf.net_name = user.net_name;
    userSelf.role = user.role;
    userSelf.uid = user.uid;  */



    let net_name =user.net_name;   //局部变量,保存当前连接的是谁
    let icon =user.icon;   //局部变量,保存当前连接的是谁
    let uid =user.uid;   //局部变量,保存当前连接的是谁
    let role =user.role;   //局部变量,保存当前连接的是谁




    // console.log('有人连接');
  // 每建立连接一次,在线人数加一
  count++
  //这里是发送消息
  // on用来监听客户端message事件,回调函数处理。
  socket.on('message', function (msg) {
 
    // 如果在这里通过url解析的username来改变下面33行即将渲染的name,会出现异步问题。name还没有赋值就被传到客户端
    // 所以通过ajax请求,先让后端拿到username,然后再做提示信息的渲染
   // console.log(msg.net_name+'('+msg.role+')'+':'+ msg.inpval);
    // 将客户端发送来的消息中转给所有客户端
    io.emit('message', msg)
  });
  // loginin是自定义事件,第二个参数返回数据对象用于渲染,用于登陆后向客户端发送用户登录信息
  io.emit('loginin',{count,des:`温馨提示:${net_name}(${role})进入聊天室 ${new Date().Format("yyyy-MM-dd-hh:mm")}`,userList:userList})
  //io.emit('loginin',{count,des:{a:'哈哈哈'}})
  //登陆后向客户端发送用户退出信息
  //当断开连接disconnect,前端会执行下面这个loginout函数
  socket.on('disconnect', function (value) {
    //console.log(value);
    // loginout是自定义事件,第二个参数返回数据对象用于渲染
    count--
    //在线用户数组减去一个
    for(let i=0;i<=userList.length-1;i++){
      if(userList[i].uid == uid){
        userList.splice(i,1)
      }
    }


   io.emit('loginout',{count,des:`温馨提示:${net_name}(${role})退出聊天室  ${new Date().Format("yyyy-MM-dd-hh:mm")}`,userList:userList})

    // 连接每断开一次,在线人数减一
  })
});
http.listen(8849, function () {
  console.log('websocket连接成功!')
})

结语

我的哔哩哔哩空间 Gitee仓库地址:全部css、js特效源码 其它文章:

~关注我看更多简单创意特效:

文字烟雾效果 html+css+js

环绕倒影加载特效 html+css

气泡浮动背景特效 html+css

简约时钟特效 html+css+js

赛博朋克风格按钮 html+css

仿网易云官网轮播图 html+css+js

水波加载动画 html+css

导航栏滚动渐变效果 html+css+js

书本翻页 html+css

3D立体相册 html+css

霓虹灯绘画板效果 html+css+js

记一些css属性总结(一)

Sass总结笔记

…等等

进我主页看更多~