commit 283fc7a4b90afcf145baf249c76d10cd14579333 Author: starlight_0208 <2682994272@qq.com> Date: Mon Jul 14 19:23:51 2025 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e35421 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +data.db +package-lock.json \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..0387562 --- /dev/null +++ b/app.js @@ -0,0 +1,216 @@ +import express from "express"; +import http from "http"; +import log4js from "log4js"; +import { Server } from "socket.io"; +import { Sequelize, Model, DataTypes, Op } from "sequelize"; + +const PORT = process.env.PORT || 5691; +let dbReady = false; + +const app = express(); +const server = http.createServer(app); +const io = new Server(server, { + cors: { + origin: "*", + methods: ["GET", "POST"], + }, +}); +const sequelize = new Sequelize({ + dialect: 'sqlite', + storage: './data.db' +}) +const logger = log4js.getLogger("app"); + +app.use(express.static("static")) +logger.level = process.env.LOG_LEVEL || "debug"; + +// 初始化在线列表 +let onlineList = []; +let clientMap = new Map(); + +class Response { + constructor(status, msg) { + this.status = status + this.msg = msg + } +} + +class Messgae extends Model {} +Messgae.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + sender: { + type: DataTypes.STRING, + allowNull: false + }, + message: { + type: DataTypes.STRING, + allowNull: false + }, + sendtime: { + type: DataTypes.INTEGER, + allowNull: false + } +}, { + sequelize, + modelName: "Message" +}); + +try { + await sequelize.authenticate(); + logger.info("Database connection has been established successfully."); + dbReady = true; +} catch (err) { + logger.error("Unable to connect to the database:" + err); +} + +try { + if(dbReady) { + await Messgae.sync(); + logger.info("Table created successfully."); + } +} catch (error) { + logger.error("Unable to create table:" + error) +} + +function saveToDatabase(sender, msg, time) { + const messageItem = Messgae.build({ + sender: sender, + message: msg, + sendtime: time + }); + messageItem.save().then(() => { + logger.debug(`message saved, sender: ${sender}, message: ${msg}`) + }).catch((err) => { + logger.error(`message save failed, sender: ${sender}, message: ${msg}, error: ${err}`) + }) +} + +function loadHistory(limit = 10) { + if (!dbReady) { + return []; + } + return Messgae.findAll({ + limit: limit, + order: [['sendtime', 'DESC']], + attributes: ['id', 'sender', 'message', 'sendtime'] + }) +} + +function queryHistory(start, end, limit, order) { + if (!dbReady) { + return []; + } + return Messgae.findAll({ + limit: limit, + order: [['sendtime', order]], + attributes: ['id', 'sender', 'message', 'sendtime'], + where: { + sendtime: { + [Op.between]: [start, end] + } + } + }); +} + +app.get("/online", (req, res) => { + res.status(200).send({ + online: onlineList.length, + user: onlineList + }) +}) + +app.get("/history", (req, res) => { + if (!dbReady) { + res.status(500).send({ + status: 500, + msg: "database is not ready", + data: null + }) + } + if (!req.query.limit || isNaN(req.query.limit) || req.query.limit <= 0 || req.query.limit > 100) { + req.query.limit = 10; + } + loadHistory(req.query.limit).then((data) => { + res.status(200).send({ + status: 200, + msg: "success", + data: data + }) + }).catch((err) => { + logger.error(`database error: ${err}`) + res.status(500).send({ + status: 500, + msg: "database error", + data: null + }) + }) +}) + +io.on("connection", (socket) => { + logger.info(`A client connected, id: ${socket.id}`) + socket.on("system", data => { + if (!data) { + socket.emit("system", JSON.stringify(new Response(false, "data payload is empty."))) + return; + } + if (clientMap.has(socket.id)) { + socket.emit("system", JSON.stringify(new Response(false, "you are already online."))) + return; + } + try { + const { opt, args } = JSON.parse(data); + console.log(opt, args) + if (opt == "signin") { + if (!args || !args.username) { + socket.emit("system", JSON.stringify(new Response(false, "username is empty."))) + return; + } + const { username } = args + if (onlineList.indexOf(username) != -1) { + socket.emit("system", JSON.stringify(new Response(false, "username is already taken."))) + return; + } + socket.username = username; + onlineList.push(username); + clientMap.set(socket.id, username); + socket.emit("system", JSON.stringify(new Response(true, `welcome, ${socket.username}`))) + io.emit("notice", JSON.stringify(new Response(true, `${socket.username} join the chat.`))) + logger.debug(`socket(${socket.id}) -- ${username} signed in.`) + } + + } catch (err) { + logger.error(`socket(${socket.id}) -- ${err}.`) + socket.emit("system", JSON.stringify(new Response(false, "data payload is invaild."))) + return; + } + console.log(clientMap) + console.log(onlineList) + }) + + socket.on("msg", data => { + if (!socket.username) { + socket.emit("system", JSON.stringify(new Response(false, "you are not signed in."))); + return; + } + logger.debug(`socket(${socket.id}) -- ${data}`) + io.emit("msg", JSON.stringify({sender: socket.username, msg: data})) + saveToDatabase(socket.username, data, new Date().getTime()) + }) + + socket.on("disconnect", (reason, description) => { + logger.info(`A client disconnected, id: ${socket.id}`) + logger.trace(`socket(${socket.id}) -- reason: ${reason}, description: ${description}`); + if (clientMap.has(socket.id)) clientMap.delete(socket.id); + // TODO: 这个算法有点不安全 + if (onlineList.includes(socket.username)) onlineList.splice(onlineList.indexOf(socket.id)); + io.emit("notice", JSON.stringify(new Response(true, `${socket.username} left the chat.`))); + }) +}); + +server.listen(PORT, () => { + logger.info(`Server running on port ${PORT}`); +}); diff --git a/package.json b/package.json new file mode 100644 index 0000000..9e45b80 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "chat-server", + "version": "3.0.0-alpha", + "description": "a simple chat server using socket.io", + "main": "app.js", + "type": "module", + "scripts": { + "test": "node app.js", + "debug": "nodemon --exec node app.js" + }, + "author": "star", + "license": "MIT", + "dependencies": { + "express": "^5.1.0", + "log4js": "^6.9.1", + "sequelize": "^6.37.7", + "socket.io": "^4.8.1", + "sqlite3": "^5.1.7" + }, + "devDependencies": { + "nodemon": "^3.1.10" + } +} diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..4c55ef2 --- /dev/null +++ b/static/index.html @@ -0,0 +1,219 @@ + + + + + + + Document + + + + +
+
+
在线列表(在线人数:NaN
+
+
+
+
系统消息
+
+
+
+
用户消息
+
+
+
+
+
+ + +
+
+
Status:
+ + +
+
+ + +
+
+ + + + + \ No newline at end of file