mirror of
https://github.com/LingChair/LingChair-V0.git
synced 2025-12-07 17:45:49 +08:00
chore: init
This commit is contained in:
121
server_src/api-msgs.js
Normal file
121
server_src/api-msgs.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 通讯辅助类
|
||||
*/
|
||||
|
||||
const io = require("./iolib")
|
||||
const hash = require("./hashlib")
|
||||
const vals = require("./val")
|
||||
const users = require("./api-users")
|
||||
|
||||
let getSameHashedValue = (a, b) => {
|
||||
let _a = [hash.md5(a) + hash.sha256(a), hash.md5(b) + hash.sha256(b)].sort()
|
||||
let [_1, _2] = _a
|
||||
return hash.sha256hex(hash.sha256hex(_1) + hash.sha256hex(_2))
|
||||
}
|
||||
|
||||
let getSingleChatDir = (a, b) => {
|
||||
return vals.LINGCHAIR_SINGLE_MESSAGE_DIR + "/" + getSameHashedValue(a, b)
|
||||
}
|
||||
|
||||
let apis = {
|
||||
// 储存单聊消息: 操作者, 访问密钥, 发送至, 消息内容
|
||||
// 消息存储方式为计次直接储存, 每一个消息都有对应的 ID
|
||||
// 读取某一段落时使用遍历方式
|
||||
// @API
|
||||
sendSingleMsg: (name, accessToken, target, msg) => {
|
||||
if (!users.checkAccessToken(name, accessToken))
|
||||
return { code: -1, msg: "访问令牌错误" }
|
||||
|
||||
if (!users.isUserExists(target))
|
||||
return { code: -1, msg: "目标用户不存在" }
|
||||
|
||||
if (msg.trim() == "")
|
||||
return { code: -1, msg: "不是有内容的消息我不要" }
|
||||
|
||||
let fileDir = getSingleChatDir(name, target)
|
||||
io.mkdirs(fileDir)
|
||||
|
||||
let countFile = io.open(fileDir + "/count.txt", "rw")
|
||||
if (!io.exists(fileDir + "/count.txt"))
|
||||
countFile.write("0")
|
||||
|
||||
let count = parseInt(countFile.read())
|
||||
count += 1
|
||||
let time = Date.now()
|
||||
io.open(fileDir + "/msg_" + count + ".json", "w").writeJson({
|
||||
name: name,
|
||||
msg: msg,
|
||||
msgid: count,
|
||||
time: time,
|
||||
}).close()
|
||||
|
||||
countFile.write(count + "")
|
||||
|
||||
return { code: 0, msg: "成功", msgid: count, time: time }
|
||||
},
|
||||
// 读取消息记录
|
||||
// 从起始点到结束点读取,由最新到最老(计次越大越新)
|
||||
// 不提供 startId 则默认从最新计次往前数
|
||||
// 若超过 limit 计次范围, 直接终止遍历
|
||||
// @API
|
||||
getSingleMsgHistroy: (name, accessToken, target, sid, limit) => {
|
||||
if (!users.checkAccessToken(name, accessToken))
|
||||
return { code: -1, msg: "访问令牌错误" }
|
||||
|
||||
if (!users.isUserExists(target))
|
||||
return { code: -1, msg: "目标用户不存在" }
|
||||
|
||||
let fileDir = getSingleChatDir(name, target)
|
||||
io.mkdirs(fileDir)
|
||||
let countFile = io.open(fileDir + "/count.txt", "rw")
|
||||
|
||||
if (!io.exists(fileDir + "/count.txt"))
|
||||
countFile.write("0")
|
||||
|
||||
let startId = sid
|
||||
if (startId == null)
|
||||
startId = parseInt(countFile.read().toString())
|
||||
|
||||
let list = []
|
||||
let i = startId
|
||||
let i2 = 0
|
||||
let cfn
|
||||
while(true) {
|
||||
cfn = fileDir + "/msg_" + i + ".json"
|
||||
// 1. 超过界限
|
||||
// 2. 超过计次
|
||||
// 3. 超过最大限度
|
||||
if ((!io.exists(cfn)) || i2 > limit || i2 > 100) break
|
||||
try {
|
||||
let data = io.open(cfn, "r").readJson()
|
||||
list.unshift(data)
|
||||
} catch (e) {
|
||||
return { code: -2, msg: e }
|
||||
}
|
||||
i--
|
||||
i2++
|
||||
}
|
||||
|
||||
return { code: 0, msg: "成功", histroy: list }
|
||||
},
|
||||
|
||||
// 上传图片: 操作者, 访问密钥, 发送至, 图片
|
||||
// 未来需要一些操作来删除未使用的图片文件
|
||||
// @API
|
||||
uploadImage: (name, accessToken, target, msg) => {
|
||||
if (!users.checkAccessToken(name, accessToken))
|
||||
return { code: -1, msg: "访问令牌错误" }
|
||||
|
||||
if (!users.isUserExists(target))
|
||||
return { code: -1, msg: "目标用户不存在" }
|
||||
|
||||
let fileDir = getSingleChatDir(name, target) + "/images/"
|
||||
io.mkdirs(fileDir)
|
||||
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = apis
|
||||
195
server_src/api-users.js
Normal file
195
server_src/api-users.js
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 用户辅助类
|
||||
*/
|
||||
|
||||
const io = require("./iolib")
|
||||
const hash = require("./hashlib")
|
||||
const vals = require("./val")
|
||||
|
||||
// 获取用户资料所在的路径
|
||||
let getUserPath = (name) => {
|
||||
return vals.LINGCHAIR_DATA_DIR + "/users/" + name
|
||||
}
|
||||
|
||||
// 用户是否存在
|
||||
let isUserExists = (name) => {
|
||||
return io.exists(getUserPath(name))
|
||||
}
|
||||
|
||||
let apis = {
|
||||
isUserExists: isUserExists,
|
||||
|
||||
// ================================
|
||||
// 无需令牌的 API
|
||||
// ================================
|
||||
|
||||
// 创建账号: 账号, 密码 返回账号唯一 ID 和成功信息 失败返回 null 和原因
|
||||
// 账号文件结构: {uid: 10000, name: "GenShin", nick: "Impact", passwd: "SHA-256 + MD5"}
|
||||
// 注意: 密码在客户端也应该经过哈希处理(SHA256 + MD5)
|
||||
// @APi
|
||||
signUp: (name, passwd) => {
|
||||
if (passwd == null || name == null)
|
||||
return { msg: "必须输入 账号和密码", code: -1 }
|
||||
|
||||
let path = getUserPath(name)
|
||||
if (isUserExists(name))
|
||||
return { msg: "用户账号名重复", code: -1 }
|
||||
|
||||
io.mkdirs(path)
|
||||
|
||||
let idCount = io.open(vals.LINGCHAIR_USERS_COUNT_FILE)
|
||||
let uid = parseInt(idCount.read("*a"))
|
||||
idCount.write((uid + 1) + "").close()
|
||||
|
||||
io.open(path + "/user.json").writeJson({
|
||||
uid: uid,
|
||||
name: name,
|
||||
nick: null,
|
||||
passwd: hash.sha256(passwd) + hash.md5(passwd),
|
||||
}).close()
|
||||
|
||||
return { uid: uid, msg: "成功", code: 0 }
|
||||
},
|
||||
|
||||
// 登录账号: 账号, 密码 返回刷新令牌 失败返回 null 和原因
|
||||
// 注意: 密码在客户端应该经过哈希处理(SHA256 + MD5)
|
||||
// @API
|
||||
signIn: (name, passwd) => {
|
||||
if (passwd == null || name == null)
|
||||
return { msg: "必须输入 账号和密码", code: -1 }
|
||||
|
||||
if (!isUserExists(name))
|
||||
return { msg: "用户不存在", code: -1 }
|
||||
|
||||
if (apis.getPassWordHashedRaw(name) !== (hash.sha256(passwd) + hash.md5(passwd)))
|
||||
return { msg: "账号所对应的密码错误", code: -1 }
|
||||
|
||||
return { msg: "成功", code: 0, refreshToken: apis.getRefreshToken(name, apis.getPassWordHashed(name)) }
|
||||
},
|
||||
|
||||
// 获取刷新令牌: 账号,密码 返回刷新令牌
|
||||
// 注意: 密码在客户端也应该经过哈希处理(SHA256 + MD5)
|
||||
// 刷新令牌算法: 哈希(用户ID + 当前年 + 当前月 + 密码 + 盐)
|
||||
// 有效期: 一个月
|
||||
getRefreshToken: (name, passwd) => {
|
||||
let d = new Date()
|
||||
let raw = name + d.getFullYear() + d.getMonth() + passwd + "LINGCHAIR-TEST-DEMO"
|
||||
return hash.sha256(raw) + hash.md5(raw)
|
||||
},
|
||||
|
||||
// 获取访问令牌: 账号,刷新令牌 返回访问令牌
|
||||
// 注意: 密码在客户端也应该经过哈希处理(SHA256 + MD5)
|
||||
// 刷新令牌算法: 哈希(用户ID + 当前年 + 当前月 + 密码 + 盐)
|
||||
// 有效期: 一天
|
||||
getAccessTokenNonApi: (name, rt) => {
|
||||
if (!apis.checkRefreshToken(name, rt))
|
||||
return null
|
||||
let date = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'numeric', day: 'numeric' })
|
||||
return hash.sha256(name + date) + hash.md5(rt + date + "LINGCHAIR-ACCESS-TEST-DEMO")
|
||||
},
|
||||
|
||||
// 获取密码(已被哈希处理) 返回密码
|
||||
// 在密码被设置前已经被哈希过,不需要重复
|
||||
// 算法: (SHA256 + MD5)
|
||||
// 警告: 这是经过二次哈希的
|
||||
getPassWordHashed: (name) => {
|
||||
return hash.sha256(apis.getPassWordHashedRaw(name)) + hash.md5(apis.getPassWordHashedRaw(name))
|
||||
},
|
||||
|
||||
// 请勿与上面的混淆
|
||||
// 上面的是经过第二次哈希的
|
||||
getPassWordHashedRaw: (name) => {
|
||||
return io.open(getUserPath(name) + "/user.json").readJson().passwd
|
||||
},
|
||||
|
||||
// 检测刷新令牌是否正确: 账号, 刷新令牌 返回布尔值
|
||||
// 密码在服务端经过哈希保存 不需要重复输入密码
|
||||
checkRefreshToken: (name, rt) => {
|
||||
return apis.getRefreshToken(name, apis.getPassWordHashed(name)) === rt
|
||||
},
|
||||
|
||||
// 检测访问令牌是否正确: 账号, 访问令牌 返回布尔值
|
||||
// 密码在服务端经过哈希保存 不需要重复输入密码
|
||||
checkAccessToken: (name, at) => {
|
||||
return apis.getAccessTokenNonApi(name, apis.getRefreshToken(name, apis.getPassWordHashed(name /* 就是你这个傻逼害得我找两年BUG */))) === at
|
||||
},
|
||||
|
||||
// ================================
|
||||
// 需要令牌的 API
|
||||
// ================================
|
||||
|
||||
// 获取访问令牌: 账号, 刷新令牌 返回访问令牌 失败返回 -1 和原因
|
||||
// 有效期: 一天
|
||||
// 算法: SHA256(name) + MD5(rt + 盐)
|
||||
// @Api
|
||||
getAccessToken: (name, rt) => {
|
||||
if (!apis.checkRefreshToken(name, rt))
|
||||
return { msg: "刷新令牌不正确!", code: -1 }
|
||||
|
||||
return { msg: "成功", code: 0, accessToken: apis.getAccessTokenNonApi(name, rt) }
|
||||
},
|
||||
|
||||
// 设置头像: 账号, 访问令牌, 头像数据 返回结果
|
||||
// @API
|
||||
setHeadImage: (name, at, head) => {
|
||||
if (!apis.checkAccessToken(name, at))
|
||||
return { msg: "访问令牌不正确!", code: -1 }
|
||||
|
||||
io.open(vals.LINGCHAIR_USERS_HEAD_DIR + "/" + name + ".png", "w").write(head).close()
|
||||
|
||||
return { msg: "成功", code: 0 }
|
||||
},
|
||||
|
||||
// 修改昵称
|
||||
// @APi
|
||||
setNick: (name, at, nick) => {
|
||||
if (!apis.checkAccessToken(name, at))
|
||||
return { msg: "访问令牌不正确!", code: -1 }
|
||||
|
||||
let path = getUserPath(name)
|
||||
let configIo = io.open(path + "/user.json", "rw")
|
||||
|
||||
let config = configIo.readJson()
|
||||
config.nick = nick
|
||||
configIo.writeJson(config)
|
||||
|
||||
configIo.close()
|
||||
|
||||
return { msg: "成功", code: 0 }
|
||||
},
|
||||
|
||||
// 取联系人列表(好友): 账号, 访问令牌 返回好友列表
|
||||
getFriendsNonApi: (name, at) => {
|
||||
let file = getUserPath(name) + "/friends.json"
|
||||
if (!io.exists(file))
|
||||
io.open(file, "w").writeJson({list: [name]}).close()
|
||||
|
||||
return io.open(file, "r").readJson().list
|
||||
},
|
||||
|
||||
// 取用户昵称: 账号 返回昵称
|
||||
getNickNonApi: (name) => {
|
||||
let file = getUserPath(name) + "/user.json"
|
||||
|
||||
return io.open(file, "r").readJson().nick
|
||||
},
|
||||
|
||||
// 取昵称: 账号 返回昵称
|
||||
// @API
|
||||
getNick: (name, at) => {
|
||||
return { msg: "成功", code: 0, nick: apis.getNickNonApi(name)}
|
||||
},
|
||||
|
||||
// 取联系人列表(好友): 账号, 访问令牌 返回好友列表
|
||||
// @API
|
||||
getFriends: (name, at) => {
|
||||
if (!apis.checkAccessToken(name, at))
|
||||
return { msg: "访问令牌不正确!", code: -1 }
|
||||
|
||||
return { msg: "成功", code: 0, friends: apis.getFriendsNonApi(name, at)}
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = apis
|
||||
13
server_src/color.js
Normal file
13
server_src/color.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 控制台颜色辅助类
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
none: "\033[0m",
|
||||
red: "\033[1;31m",
|
||||
green: "\033[1;32m",
|
||||
yellow: "\033[1;33m",
|
||||
blue: "\033[1;34m",
|
||||
}
|
||||
16
server_src/hashlib.js
Normal file
16
server_src/hashlib.js
Normal file
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 哈希辅助类
|
||||
*/
|
||||
|
||||
const crypto = require("crypto")
|
||||
|
||||
let apis = {
|
||||
sha256: (data) => crypto.createHash("sha256").update(data).digest("base64"),
|
||||
md5: (data) => crypto.createHash("md5").update(data).digest("base64"),
|
||||
sha256hex: (data) => crypto.createHash("sha256").update(data).digest("hex"),
|
||||
md5hex: (data) => crypto.createHash("md5").update(data).digest("hex"),
|
||||
}
|
||||
|
||||
module.exports = apis
|
||||
17
server_src/httpApi.js
Normal file
17
server_src/httpApi.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 铃之椅 Node 服务端
|
||||
*/
|
||||
|
||||
// 不得不说 express 太强了
|
||||
|
||||
const vals = require("./val")
|
||||
const express = require("express")
|
||||
|
||||
let api = express()
|
||||
|
||||
api.use("/", express.static("ling_chair_http"))
|
||||
api.use("/users_head/", express.static(vals.LINGCHAIR_DATA_DIR + "/users_head"))
|
||||
|
||||
module.exports = api
|
||||
48
server_src/iolib.js
Normal file
48
server_src/iolib.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 更简单地使用 FileSystem 库
|
||||
*/
|
||||
|
||||
const fs = require("fs")
|
||||
|
||||
class IoImpl {
|
||||
constructor(p, m) {
|
||||
this.path = p
|
||||
this.mode = m
|
||||
}
|
||||
write(byteOrString) {
|
||||
fs.writeFileSync(this.path, byteOrString)
|
||||
return this
|
||||
}
|
||||
read(type) {
|
||||
// TODO: impentments this method
|
||||
return fs.readFileSync(this.path)
|
||||
}
|
||||
close() {
|
||||
delete this.path
|
||||
delete this.mode
|
||||
delete this.read
|
||||
delete this.write
|
||||
}
|
||||
readJson() {
|
||||
return JSON.parse(this.read("*a"))
|
||||
}
|
||||
writeJson(data) {
|
||||
return this.write(JSON .stringify(data))
|
||||
}
|
||||
}
|
||||
|
||||
let apis = {
|
||||
open: (path, mode) => {
|
||||
return new IoImpl(path, mode)
|
||||
},
|
||||
mkdirs: (path) => {
|
||||
try {fs.mkdirSync(path, { recursive: true })}catch(e){}
|
||||
},
|
||||
exists: (path) => {
|
||||
return fs.existsSync(path)
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = apis
|
||||
93
server_src/main.js
Normal file
93
server_src/main.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 铃之椅 Node 服务端
|
||||
*/
|
||||
|
||||
console.log("正在初始化...")
|
||||
|
||||
const log = (t) => {
|
||||
console.log("[" + new Date().toLocaleTimeString('en-US', { hour12: false }) + "] " + t)
|
||||
}
|
||||
|
||||
const sio = require("socket.io")
|
||||
const http = require("http")
|
||||
const https = require("https")
|
||||
const fs = require("fs")
|
||||
const process = require("process")
|
||||
const vals = require("./val")
|
||||
const color = require("./color")
|
||||
|
||||
//定义 Http 服务器回调
|
||||
let httpServerCallback = require("./httpApi")
|
||||
|
||||
// 定义 Socket.io 服务器回调
|
||||
let wsServerCallback = require("./wsApi")
|
||||
|
||||
let httpServer
|
||||
if (vals.LINGCHAIR_SERVER_CONFIG.useHttps)
|
||||
httpServer = https.createServer({
|
||||
key: fs.readFileSync(vals.LINGCHAIR_SERVER_CONFIG.https.key),
|
||||
cert: fs.readFileSync(vals.LINGCHAIR_SERVER_CONFIG.https.cert),
|
||||
}, httpServerCallback)
|
||||
else
|
||||
httpServer = http.createServer(httpServerCallback)
|
||||
|
||||
let wsServer = new sio.Server(httpServer)
|
||||
|
||||
const cachedClients = {}
|
||||
|
||||
let checkEmpty = (i) => {
|
||||
if (i instanceof Array) {
|
||||
for (k in i) {
|
||||
if (checkEmpty(i[k])) return true
|
||||
}
|
||||
}
|
||||
|
||||
return (i == null) || ("" === i) || (0 === i)
|
||||
}
|
||||
|
||||
wsServer.on("connect", (client) => {
|
||||
|
||||
log("客户端 " + client.handshake.address + " 已连接, 用户名(未经验证): " + client.handshake.auth.name)
|
||||
|
||||
for (const cb in wsServerCallback) {
|
||||
client.on(cb, (...args) => {
|
||||
log("客户端 " + client.handshake.address + " 对接口 [" + cb + "] 发起请求,参数为 " + JSON.stringify(args[0]))
|
||||
let callback = args[args.length - 1]
|
||||
try {
|
||||
wsServerCallback[cb](args[0], (reArgs) => {
|
||||
callback(reArgs)
|
||||
log("返回接口 [" + cb + "] 到 " + client.handshake.address + ",参数为 " + JSON.stringify(reArgs))
|
||||
}, client, cachedClients)
|
||||
} catch (e) {
|
||||
log(color.yellow + "调用接口或返回数据时出错: " + e + color.none)
|
||||
callback({ code: -1, msg: e })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
client.on("disconnect", () => {
|
||||
if (!client.handshake.auth.passCheck)
|
||||
return log("未验证的客户端 " + client.handshake.address + " 已断开, 未验证的用户名: " + client.handshake.auth.name)
|
||||
|
||||
// 为了支持多客户端登录 我豁出去了
|
||||
if (cachedClients[client.handshake.auth.name].length === 1)
|
||||
cachedClients[client.handshake.auth.name] = null
|
||||
else
|
||||
cachedClients[client.handshake.auth.name].forEach((item, index, arr) => {
|
||||
if (item == client) {
|
||||
arr.splice(index, 1)
|
||||
}
|
||||
})
|
||||
log("客户端 " + client.handshake.address + " 已断开, 用户名: " + client.handshake.auth.name)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
httpServer.listen(vals.LINGCHAIR_SERVER_CONFIG.port)
|
||||
|
||||
console.log(color.red + "=== 铃之椅 - Server ===" + color.none + "\n\r")
|
||||
console.log(color.yellow + "Github: MoonLeeeaf" + color.none)
|
||||
log(color.green + "运行服务于端口 " + vals.LINGCHAIR_SERVER_CONFIG.port + " 上," + (vals.LINGCHAIR_SERVER_CONFIG.useHttps == true ? "已" : "未") + "使用 HTTPS" + color.none)
|
||||
log(color.green + "服务已启动..." + color.none)
|
||||
56
server_src/val.js
Normal file
56
server_src/val.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 铃之椅 Node 服务端
|
||||
*/
|
||||
|
||||
const io = require("./iolib")
|
||||
|
||||
let vals = {}
|
||||
|
||||
// 配置目录
|
||||
vals.LINGCHAIR_CONFIG_DIR = "ling_chair_config"
|
||||
// HTTP 服务器资源目录
|
||||
vals.LINGCHAIR_HTTP_DIR = "ling_chair_http"
|
||||
// 服务端配置
|
||||
vals.LINGCHAIR_SERVER_CONFIG_FILE = vals.LINGCHAIR_CONFIG_DIR + "/server.json"
|
||||
|
||||
// 主要数据目录
|
||||
vals.LINGCHAIR_DATA_DIR = "ling_chair_data"
|
||||
|
||||
// 用户数据
|
||||
vals.LINGCHAIR_USERS_DATA_DIR = vals.LINGCHAIR_DATA_DIR + "/users"
|
||||
// 用户头像
|
||||
vals.LINGCHAIR_USERS_HEAD_DIR = vals.LINGCHAIR_DATA_DIR + "/users_head"
|
||||
|
||||
// 群聊消息
|
||||
vals.LINGCHAIR_GROUP_MESSAGE_DIR = vals.LINGCHAIR_DATA_DIR + "/messages/group"
|
||||
// 单聊消息
|
||||
vals.LINGCHAIR_SINGLE_MESSAGE_DIR = vals.LINGCHAIR_DATA_DIR + "/messages/single"
|
||||
|
||||
// 用户 ID 计次
|
||||
vals.LINGCHAIR_USERS_COUNT_FILE = vals.LINGCHAIR_USERS_DATA_DIR + "/count.txt"
|
||||
|
||||
// 创建必备目录
|
||||
io.mkdirs(vals.LINGCHAIR_CONFIG_DIR)
|
||||
io.mkdirs(vals.LINGCHAIR_USERS_DATA_DIR)
|
||||
io.mkdirs(vals.LINGCHAIR_USERS_HEAD_DIR)
|
||||
io.mkdirs(vals.LINGCHAIR_GROUP_MESSAGE_DIR)
|
||||
io.mkdirs(vals.LINGCHAIR_SINGLE_MESSAGE_DIR)
|
||||
|
||||
// 生成服务端配置文件
|
||||
if (!io.exists(vals.LINGCHAIR_SERVER_CONFIG_FILE)) io.open(vals.LINGCHAIR_SERVER_CONFIG_FILE, "w").write(JSON.stringify({
|
||||
useHttps: false,
|
||||
port: 3601,
|
||||
bindAddress: "",
|
||||
https: {
|
||||
key: "",
|
||||
cert: "",
|
||||
},
|
||||
})).close()
|
||||
if (!io.exists(vals.LINGCHAIR_USERS_COUNT_FILE)) io.open(vals.LINGCHAIR_USERS_COUNT_FILE, "w").write("10000").close()
|
||||
|
||||
// 加载服务端配置文件
|
||||
vals.LINGCHAIR_SERVER_CONFIG = JSON.parse(io.open(vals.LINGCHAIR_SERVER_CONFIG_FILE, "r").read("*a"))
|
||||
|
||||
module.exports = vals
|
||||
214
server_src/wsApi.js
Normal file
214
server_src/wsApi.js
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* ©2024 满月叶
|
||||
* Github: MoonLeeeaf
|
||||
* 铃之椅 Node 服务端
|
||||
*/
|
||||
|
||||
const log = (t) => {
|
||||
console.log("[" + new Date().toLocaleTimeString('en-US', { hour12: false }) + "] " + t)
|
||||
}
|
||||
|
||||
const msgs = require("./api-msgs")
|
||||
const users = require("./api-users")
|
||||
const color = require("./color")
|
||||
|
||||
let checkEmpty = (i) => {
|
||||
if (i instanceof Array) {
|
||||
for (k in i) {
|
||||
if (checkEmpty(i[k])) return true
|
||||
}
|
||||
}
|
||||
|
||||
return (i == null) || ("" === i) || (0 === i)
|
||||
}
|
||||
|
||||
/*
|
||||
* Api 规范:
|
||||
* 1. 禁止中文 拼音
|
||||
* 2. 一个 Api 做一件事 同一组 Api 用注释行分隔
|
||||
* 3. 尽可能简单易懂 或者打注释
|
||||
* 4. 保证客户端可用
|
||||
*/
|
||||
|
||||
// Api 调用:
|
||||
|
||||
// 一般规定, code=0 正常, code=-1 异常, code=-2 运行时错误 另外还需要 msg="any"
|
||||
|
||||
// 可以随便 return 进行函数中断 因为这里的调用不会取返回值
|
||||
|
||||
let api = {
|
||||
// ---------- 用户 API ----------
|
||||
|
||||
// 验证
|
||||
// 调用方法自己看
|
||||
"user.auth": (a, cb, client, cachedClients) => {
|
||||
if (checkEmpty([a.name, a.refreshToken]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
if (!users.checkRefreshToken(a.name, a.refreshToken))
|
||||
return cb({ code: -1, msg: "刷新令牌错误" })
|
||||
|
||||
log(color.yellow + "客户端 " + client.handshake.address + " 完成了用户 " + a.name + " 的验证" + color.none)
|
||||
|
||||
// 更新映射
|
||||
client.handshake.auth.passCheck = true
|
||||
if (cachedClients[a.name] == null)
|
||||
cachedClients[a.name] = []
|
||||
cachedClients[a.name].push(client)
|
||||
|
||||
cb({ code: 0, msg: "成功" })
|
||||
},
|
||||
|
||||
// 注册
|
||||
// {name: 账号, nick: 昵称, passwd: 密码} 返回 {data: {uid: 账号ID}}
|
||||
// 密码在客户端应该经过哈希处理 算法为 SHA256+MD5
|
||||
// 客户端在注册成功之后应该引导用户登录
|
||||
"user.signUp": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.passwd]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { uid, msg, code } = users.signUp(a.name, a.passwd)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0, data: { uid: uid } })
|
||||
},
|
||||
// 登录
|
||||
// {name: 账号, passwd: 密码} 返回 {data: {refreshToken: 刷新令牌}}
|
||||
// 密码在客户端应该经过哈希处理 算法为 SHA256+MD5
|
||||
"user.signIn": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.passwd]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
|
||||
let { refreshToken, msg, code } = users.signIn(a.name, a.passwd)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
|
||||
cb({ msg: msg, code: 0, data: { refreshToken: refreshToken } })
|
||||
},
|
||||
|
||||
// 获取访问令牌
|
||||
// {name: 账号, refreshToken: 刷新令牌} 返回 {data: {accessToken: 访问令牌}}
|
||||
"user.getAccessToken": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.refreshToken]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { accessToken, msg, code } = users.getAccessToken(a.name, a.refreshToken)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0, data: { accessToken: accessToken } })
|
||||
},
|
||||
|
||||
// 上传头像
|
||||
// {name: 账号, accessToken: 访问令牌, headImage: 头像数据} 返回 {}
|
||||
"user.setHeadImage": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.accessToken, a.headImage]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { msg, code } = users.setHeadImage(a.name, a.accessToken, a.headImage)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0 })
|
||||
},
|
||||
|
||||
// 修改昵称
|
||||
"user.setNick": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.accessToken, a.nick]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { msg, code } = users.setNick(a.name, a.accessToken, a.nick)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0 })
|
||||
},
|
||||
|
||||
// ---------- 联系人 API --------
|
||||
|
||||
// 获取好友列表
|
||||
// {name: 账号, accessToken: 访问令牌} 返回 {friends: []}
|
||||
"user.getFriends": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.accessToken]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { msg, code, friends } = users.getFriends(a.name, a.accessToken)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0, data: { friends: friends } })
|
||||
},
|
||||
|
||||
"user.getNick": (a, cb) => {
|
||||
if (checkEmpty([a.name]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { msg, code, nick } = users.getNick(a.name)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0, data: { nick: nick } })
|
||||
},
|
||||
|
||||
// ---------- 通讯 API ----------
|
||||
|
||||
// 单聊发送消息
|
||||
// {name: 当前用户, target: 发送到, accessToken: 访问密钥, msg: 消息内容}
|
||||
// 2024.3.30: 支持对方收到消息
|
||||
"user.sendSingleMsg": (a, cb, c, cache) => {
|
||||
if (checkEmpty([a.name, a.target, a.accessToken, a.msg]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { msg, code, msgid, time } = msgs.sendSingleMsg(a.name, a.accessToken, a.target, a.msg)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
// 微机课闲的没事干玩玩 发现私聊会多发一个(一个是本地的, 另一个是发送成功的) 选择一个关掉就好了
|
||||
// 这里我选择客户端, 否则没法多设备同步
|
||||
let args = {
|
||||
target: a.name,
|
||||
msg: {
|
||||
msgid: msgid,
|
||||
time: time,
|
||||
msg: a.msg,
|
||||
name: a.name,
|
||||
},
|
||||
type: "single",
|
||||
}
|
||||
|
||||
if (cache[a.target] != null)
|
||||
cache[a.target].forEach((v) => {
|
||||
v.emit("msg.receive", args, () => { })
|
||||
log("尝试向客户端 " + v.handshake.address + " 发送事件 [msg.receive], 参数为 " + JSON.stringify(args))
|
||||
})
|
||||
|
||||
cb({ msg: msg, code: 0, data: { time: time } })
|
||||
},
|
||||
|
||||
// 单聊获取历史记录
|
||||
// {name: 当前用户, target: 聊天目标, accessToken: 访问密钥, startId: 计次开始的msgid, limit: 最大返回数(最大100)}
|
||||
"user.getSingleChatHistroy": (a, cb) => {
|
||||
if (checkEmpty([a.name, a.target, a.accessToken, a.limit]))
|
||||
return cb({ msg: "参数缺失", code: -1 })
|
||||
|
||||
let { msg, code, histroy } = msgs.getSingleMsgHistroy(a.name, a.accessToken, a.target, a.startId, a.limit)
|
||||
|
||||
if (code !== 0)
|
||||
return cb({ msg: msg, code: code })
|
||||
|
||||
cb({ msg: msg, code: 0, data: { histroy: histroy } })
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = api
|
||||
Reference in New Issue
Block a user