diff --git a/app.js b/app.js index f3b05c5..1dd2416 100644 --- a/app.js +++ b/app.js @@ -5,8 +5,9 @@ import apiRouter from "./router/api.js" import { Server } from "socket.io"; import { saveToDatabase } from "./utils/storage.js"; import { onlineList, clientMap } from "./data.js"; +import { verifyToken } from "./utils/auth.js"; -const PORT = process.env.PORT || 5691; +const PORT = process.env.PORT || 1111; const app = express(); @@ -52,25 +53,37 @@ io.on("connection", (socket) => { const { opt, args } = JSON.parse(data); logger.trace(opt, args) if (opt == "signin") { - if (!args || !args.username) { - socket.emit("system", JSON.stringify(new Response(false, "username is empty."))) + if (!args || !args.token) { + socket.emit("system", JSON.stringify(new Response(false, "token is empty."))) return; } - const { username } = args - if (username.length > 20) { - socket.emit("system", JSON.stringify(new Response(false, "username is too long."))) - } - if (onlineList.indexOf(username) != -1) { - socket.emit("system", JSON.stringify(new Response(false, "username is already taken."))) + const { token } = args + // const username = token.trim(); + verifyToken(token) + .then(res => { + if (res.data.code != 200) { + socket.emit("system", JSON.stringify(new Response(false, "token is invaild."))) + username = res.data.UserName; + uid = res.data.ID; + return; + } + if (onlineList.indexOf(username) != -1) { + socket.emit("system", JSON.stringify(new Response(false, "token is already taken."))) + return; + } + socket.emit("system", JSON.stringify(new Response(true, "sign in successfully."))) + socket.emit("username", username) + socket.username = username; + onlineList.push(username); + clientMap.set(socket.id, 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, "token is invaild."))) return; - } - socket.username = username; - onlineList.push(username); - clientMap.set(socket.id, username); - socket.emit("username", socket.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) { diff --git a/package.json b/package.json index 5d9d914..e74ddd8 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "author": "star", "license": "MIT", "dependencies": { + "axios": "^1.13.2", "express": "^5.1.0", "log4js": "^6.9.1", "sequelize": "^6.37.7", diff --git a/static/index.html b/static/index.html index 9e86379..62bec6a 100644 --- a/static/index.html +++ b/static/index.html @@ -4,267 +4,63 @@ - Document - - + Chat + +
-
系统消息
-
-
用户消息
-
+
+
消息
+
-
- - +
+ +
-
在线列表
-
在线人数:NaN
+
在线用户
+
+ 在线人数:0 +
-
服务器选项
-
连接状态:
- - +
服务器状态
+
+ + + disconnected + +
+
+ + +
-
设置用户名
-
当前用户名:未设置用户名
- - +
用户信息
+
+
未登录
+ +
-
-
- -
-
- -
-
- + \ No newline at end of file diff --git a/static/script/main.js b/static/script/main.js new file mode 100644 index 0000000..06341b1 --- /dev/null +++ b/static/script/main.js @@ -0,0 +1,145 @@ +window.onload = () => { + fetchHistory() + updateLoginState(false) +} +const socket = io("ws://localhost:5691"); +socket.on("connect", () => { + console.log("Connected"); + updateState(true) + writeLogSystem("已连接到服务器", true, "client") +}); + +socket.on("system", data => { + console.log("system", data) + data = JSON.parse(data) + writeLogSystem(data.msg, data.status, "system") +}) + +socket.on("username", data => { + updateUsername(data); +}) + +socket.on("notice", data => { + console.log("notice", data) + data = JSON.parse(data) + writeLogSystem(data.msg, data.status, "notice") +}) + +socket.on("msg", data => { + console.log("user", data) + data = JSON.parse(data); + let msg = ` ${data.msg}` + writeLogUser(msg, data.sender) +}) + +socket.on("disconnect", () => { + updateState(false); + console.log("Disconnected") + writeLogSystem("已断开服务器连接", false, "client") + updateLoginState(false) +}); + +const connect = () => socket.connect() +const disconnect = () => socket.disconnect(); +const register = () => socket.emit("system", JSON.stringify({ opt: "signin", args: { username: document.getElementById("username").value } })); +const sendMsg = () => { + socket.emit("msg", document.getElementById("message").value) + document.getElementById("message").value = "" +} + +const updateState = (value) => { + const statEl = document.getElementById('stat'); + const statText = document.getElementById('stat-text'); + if (value) { + statEl.className = 'status-badge connected'; + statText.textContent = 'connected'; + } else { + statEl.className = 'status-badge disconnected'; + statText.textContent = 'disconnected'; + } +} + +document.getElementById("message").addEventListener("keydown", (e) => { + if (e.key === "Enter") { + sendMsg(); + } +}); + +const updateUsername = data => { + document.getElementById("current-username").textContent = data; + updateLoginState(true, data); +} + +const updateLoginState = (isLoggedIn, username = '') => { + const usernameEl = document.getElementById("current-username"); + const loginPrompt = document.getElementById("login-prompt"); + + if (isLoggedIn) { + usernameEl.textContent = username || '已登录'; + usernameEl.style.display = 'block'; + if (loginPrompt) loginPrompt.style.display = 'none'; + } else { + usernameEl.textContent = '未登录'; + if (loginPrompt) loginPrompt.style.display = 'block'; + } +} + +const showLogin = () => { + const username = prompt("请输入用户名:"); + if (username && username.trim()) { + socket.emit("system", JSON.stringify({ opt: "signin", args: { username: username.trim() } })); + } +} + +function fetchHistory() { + fetch("/history") + .then(res => res.json()) + .then(res => { + res.data.reverse().forEach(element => { + writeLogUser(element.message, element.sender) + }); + }) + .catch(err => { + console.log(err) + }) +} + +function fetchOnline() { + fetch("/online").then(res => res.json()).then(res => { + const c = document.getElementById('online'); + document.getElementById('online-number').textContent = res.online; + c.innerHTML = ""; + res.user.forEach(element => { + const u = document.createElement('span'); + u.textContent = element; + c.append(u); + }); + }); +} + +const onlineChecker = setInterval(() => { + fetchOnline(); +}, 1000) + +function writeLogSystem(msg, status = true, topic = "system") { + const c = document.getElementById('sysmsg'); + const m = document.createElement('span'); + m.style.color = status ? 'var(--success-color)' : 'var(--danger-color)' + m.textContent = `[${new Date().toLocaleTimeString()}][${topic}] ${msg}` + c.appendChild(m); + c.scrollTop = c.scrollHeight; +} + +function writeLogUser(msg, sender) { + const c = document.getElementById('usrmsg'); + const m = document.createElement('span'); + m.textContent = `[${new Date().toLocaleTimeString()}][${sender}] ${msg}` + c.appendChild(m); + c.scrollTop = c.scrollHeight; +} + +document.getElementById("username")?.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + register(); + } +}); \ No newline at end of file diff --git a/static/style/style.css b/static/style/style.css new file mode 100644 index 0000000..1112114 --- /dev/null +++ b/static/style/style.css @@ -0,0 +1,251 @@ +:root { + --primary-color: #3b82f6; + --primary-hover: #2563eb; + --bg-color: #f8fafc; + --card-bg: #ffffff; + --border-color: #e2e8f0; + --text-primary: #1e293b; + --text-secondary: #64748b; + --success-color: #22c55e; + --danger-color: #ef4444; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'HarmonyOS Sans SC Medium', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background-color: var(--bg-color); + color: var(--text-primary); + line-height: 1.6; + padding: 20px; +} + +.title { + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 12px; +} + +.card { + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 20px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + background-color: var(--card-bg); + transition: box-shadow 0.2s ease; +} + +.card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +#container { + display: flex; + flex-direction: row; + gap: 20px; + max-width: 1400px; + margin: 0 auto; +} + +#msg-list { + flex: 1; + display: flex; + flex-direction: column; + gap: 16px; + min-width: 0; +} + +#online-list { + display: flex; + flex-direction: column; + gap: 16px; + width: 320px; + flex-shrink: 0; +} + +#sysmsg { + display: flex; + flex-direction: column; + gap: 8px; + max-height: 200px; + overflow-y: auto; + padding-right: 8px; +} + +#sysmsg::-webkit-scrollbar { + width: 4px; +} + +#sysmsg::-webkit-scrollbar-thumb { + background-color: var(--border-color); + border-radius: 4px; +} + +#usrmsg { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + overflow-y: auto; + max-height: calc(100vh - 280px); + padding-right: 8px; +} + +#usrmsg::-webkit-scrollbar { + width: 4px; +} + +#usrmsg::-webkit-scrollbar-thumb { + background-color: var(--border-color); + border-radius: 4px; +} + +#usrmsg > span { + padding: 10px 14px; + background-color: var(--bg-color); + border-radius: 8px; + font-size: 14px; + word-break: break-word; +} + +#online { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; + padding-top: 12px; +} + +#online > span { + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 8px 12px; + text-align: center; + font-size: 13px; + background-color: var(--bg-color); + transition: all 0.2s ease; +} + +#online > span:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +input[type="text"] { + padding: 10px 14px; + border: 1px solid var(--border-color); + border-radius: 8px; + font-size: 14px; + width: 100%; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + outline: none; +} + +input[type="text"]:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); +} + +button { + padding: 10px 20px; + background-color: var(--primary-color); + color: white; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +button:hover { + background-color: var(--primary-hover); + transform: translateY(-1px); +} + +button:active { + transform: translateY(0); +} + +.input-group { + display: flex; + gap: 8px; + align-items: center; +} + +.input-group input { + flex: 1; +} + +.input-group button { + flex-shrink: 0; +} + +.status-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 20px; + font-size: 12px; + font-weight: 500; +} + +.status-badge.connected { + background-color: rgba(34, 197, 94, 0.1); + color: var(--success-color); +} + +.status-badge.disconnected { + background-color: rgba(239, 68, 68, 0.1); + color: var(--danger-color); +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: currentColor; +} + +.username-display { + font-size: 16px; + font-weight: 600; + color: var(--primary-color); + padding: 12px 16px; + background-color: rgba(59, 130, 246, 0.05); + border-radius: 8px; + text-align: center; +} + +.login-prompt { + text-align: center; + padding: 20px; +} + +.login-prompt p { + color: var(--text-secondary); + margin-bottom: 12px; + font-size: 14px; +} + +@media screen and (max-width: 900px) { + #container { + flex-direction: column-reverse; + } + + #online-list { + width: 100%; + flex-direction: row; + flex-wrap: wrap; + } + + #online-list .card { + flex: 1; + min-width: 280px; + } +} \ No newline at end of file diff --git a/utils/auth.js b/utils/auth.js new file mode 100644 index 0000000..90f4f67 --- /dev/null +++ b/utils/auth.js @@ -0,0 +1,15 @@ +import axios from "axios" + +var apiUrl = "https://www.cloudy233.top:6699/bearer"; + +class Resp { + constructor(success, message, data = null) { + this.success = success + this.message = message + this.data = data + } +} + +export function verifyToken(token) { + return axios.get(apiUrl, {headers: {Authorization: "Bearer " + token}}) +}