添加chat功能

This commit is contained in:
539943419 committed 2024-03-22 14:35:46 +08:00
1 parent 08e7820f37
commit 127f58bd36
13 files changed
+661 -218

No files matched your search

+342 -160
View File
@@ -1,213 +1,395 @@
import express from 'express'
import cors from 'cors'
import Database from 'better-sqlite3';
import fs from "fs"
import express from "express";
import cors from "cors";
import Database from "better-sqlite3";
import fs from "fs";
import { WebSocketServer } from "ws";
import http from "http";
// import Config from "./miaoShareConfig.json"
const HOST = '0.0.0.0'
const HOST = "0.0.0.0";
// const PORT = Config.PORT
const PORT = 17057
const PORT = 17057;
const fileDir = "./temp";
const dbPath = './miaoShare.db'
const dbPath = "./miaoShare.db";
const PATH = "./dist";
// 上传文件最大大小:200MB
const uploadMaxSize=200*1024*1024
const uploadMaxSize = 200 * 1024 * 1024;
// 一天的毫秒数
const millisecondsOfTheDay = parseInt(24*3600*1000)
const millisecondsOfOneDay = parseInt(24 * 3600 * 1000);
// 文件3天过期
const LifeOfFile=millisecondsOfTheDay*3
const LifeOfFile = millisecondsOfOneDay * 3;
// const LifeOfFile=0
// 数据库相关
const db = new Database(dbPath, { verbose: null });
db.prepare(`CREATE TABLE IF NOT EXISTS id2fileName (
db.prepare(
`CREATE TABLE IF NOT EXISTS id2fileName (
id CHAR(8) PRIMARY KEY NOT NULL UNIQUE,
fileName TEXT NOT NULL,
timestamp INT NOT NULL,
freeze tinyint(1) NOT NULL,
downloadCredit INT NOT NULL
);`).run()
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`)
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`)
const stmt_getFreeze = db.prepare(`SELECT freeze FROM id2fileName WHERE id = ?`)
const stmt_getDownloadCredit = db.prepare(`SELECT downloadCredit FROM id2fileName WHERE id = ?`)
const stmt_getFreezenIDs = db.prepare(`SELECT id FROM id2fileName WHERE freeze = 1`)
const stmt_insertId2FileName = db.prepare(`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit) VALUES (?, ?, ?, ?, ?)`)
const stmt_deleteExpireData = db.prepare(`DELETE FROM id2fileName WHERE freeze=1`)
const stmt_freezeExpireData = db.prepare(`UPDATE id2fileName SET freeze=1 WHERE timestamp < ? OR downloadCredit < 1`)
const stmt_updateDownloadCredit = db.prepare(`UPDATE id2fileName SET downloadCredit=downloadCredit-1 WHERE id = ?`)
);`
).run();
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`);
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`);
const stmt_getFreeze = db.prepare(`SELECT freeze FROM id2fileName WHERE id = ?`);
const stmt_getDownloadCredit = db.prepare(`SELECT downloadCredit FROM id2fileName WHERE id = ?`);
const stmt_getFreezenIDs = db.prepare(`SELECT id FROM id2fileName WHERE freeze = 1`);
const stmt_insertId2FileName = db.prepare(`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit) VALUES (?, ?, ?, ?, ?)`);
const stmt_deleteExpireData = db.prepare(`DELETE FROM id2fileName WHERE freeze=1`);
const stmt_freezeExpireData = db.prepare(`UPDATE id2fileName SET freeze=1 WHERE timestamp < ? OR downloadCredit < 1`);
const stmt_updateDownloadCredit = db.prepare(`UPDATE id2fileName SET downloadCredit=downloadCredit-1 WHERE id = ?`);
const db_insert = (id,fileName,downloadCredit)=>{
stmt_insertId2FileName.run(id,fileName,parseInt(Date.now()),0,downloadCredit)
}
const db_getFileName = (id)=>{
try{
if(stmt_getFreeze.get(id)['freeze'] == 1)return null
return stmt_getFileName.get(id)['fileName']
}catch(e){
return null
}
}
const db_insert = (id, fileName, downloadCredit) => {
stmt_insertId2FileName.run(id, fileName, parseInt(Date.now()), 0, downloadCredit);
};
const db_getFileName = id => {
try {
if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
return stmt_getFileName.get(id)["fileName"];
} catch (e) {
return null;
}
};
// 检查是否可下载
const db_downloadable = (id)=>{
try{
// 懒
if(stmt_getDownloadCredit.get(id)['downloadCredit'] < 1)return false
if(stmt_getFreeze.get(id)['freeze'] == 1)return false
return true
}catch(e){
return false
}
}
const db_updateDownloadCredit = (id)=>{
try{
stmt_updateDownloadCredit.run(id)
return true
}catch(e){
return false
}
}
const db_downloadable = id => {
try {
// 懒
if (stmt_getDownloadCredit.get(id)["downloadCredit"] < 1) return false;
if (stmt_getFreeze.get(id)["freeze"] == 1) return false;
return true;
} catch (e) {
return false;
}
};
const db_updateDownloadCredit = id => {
try {
stmt_updateDownloadCredit.run(id);
return true;
} catch (e) {
return false;
}
};
const db_getTimestamp = (id)=>{
return stmt_getTimestamp.get(id)['timestamp']
}
const db_getTimestamp = id => {
return stmt_getTimestamp.get(id)["timestamp"];
};
// 更新存储的文件,删除已经被冻结的文件以及相关记录,然后冻结过期文件
const updateDBAndFiles = ()=>{
try{
let rows = stmt_getFreezenIDs.all()
stmt_deleteExpireData.run()
for(let row of rows){
let fileName = row['id']
let path = fileDir + '/' + fileName
try{
fs.unlink(path,(e)=>{if(e)throw e})
}catch(e){
console.log(e)
continue
}
}
stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile)
}catch(e){
console.log(e)
return
}
}
updateDBAndFiles()
const updateDBAndFiles = () => {
try {
let rows = stmt_getFreezenIDs.all();
stmt_deleteExpireData.run();
for (let row of rows) {
let fileName = row["id"];
let path = fileDir + "/" + fileName;
try {
fs.unlink(path, e => {
if (e) throw e;
});
} catch (e) {
console.log(e);
continue;
}
}
stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile);
} catch (e) {
console.log(e);
return;
}
};
updateDBAndFiles();
// 3小时更新一次
setInterval(updateDBAndFiles,1000*3600*3)
setInterval(updateDBAndFiles, 1000 * 3600 * 3);
// // 测试环境一分钟更新一次,记得注释掉
// setInterval(updateDBAndFiles, 1000 * 60);
const app = express();
var app = express();
app.use(cors());
app.options('*', cors());
var tempMap = {}
app.options("*", cors());
// var tempMap = {}
if (!fs.existsSync(fileDir)) {
fs.mkdirSync(fileDir);
fs.mkdirSync(fileDir);
}
function generateId(){
var returnStr = "",
range = 8,
charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
for(var i=0; i<range; i++){
var index = Math.round(Math.random() * 35);
returnStr += charStr.substring(index,index+1);
}
return returnStr;
function generateId(isGID) {
var returnStr = "",
range = 8,
charStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (var i = 0; i < range; i++) {
if(isGID === true && i == 0){
var index = parseInt(Math.random() * 26);
}else{
var index = parseInt(Math.random() * 36);
}
returnStr += charStr.substring(index, index + 1);
}
return returnStr;
}
// 上传文件接口
function uploadFile(req, res, fileName, downloadCredit){
let id = generateId();
const fileStream = fs.createWriteStream(fileDir + '/' + id);
db_insert(id,fileName,downloadCredit)
// tempMap[id] = fileName;
// console.log(tempMap);
req.pipe(fileStream);
res.on('data',(c)=>{console.log(c)})
req.on("end", function () {
fileStream.end();
res.statusCode = 200;
res.end(id);
});
req.on("error", err => {
fileStream.end();
res.statusCode = 500;
res.end("File save failed: " + err.message);
});
function uploadFile(req, res, fileName, downloadCredit) {
let id = generateId();
const fileStream = fs.createWriteStream(fileDir + "/" + id);
db_insert(id, fileName, downloadCredit);
// tempMap[id] = fileName;
// console.log(tempMap);
req.pipe(fileStream);
res.on("data", c => {
console.log(c);
});
req.on("end", function () {
fileStream.end();
res.statusCode = 200;
res.end(id);
});
req.on("error", err => {
fileStream.end();
res.statusCode = 500;
res.end("File save failed: " + err.message);
});
}
function downloadFile(req, res, fileId){
try {
if(db_downloadable(fileId) == false){
res.writeHead(404);
res.end(null);
return
}
db_updateDownloadCredit(fileId)
var fileName = db_getFileName(fileId)
if(fileName == null){
res.writeHead(404);
res.end(null);
return
}
let data = fs.readFileSync(fileDir + '/' + fileId);
res.writeHead(200, {
"Content-Type": "application/octet-stream ",
"File-Name": fileName,
'Content-Length':data.length,
});
res.end(data);
} catch (err) {
console.log(err);
res.writeHead(404);
res.end(null);
}
function downloadFile(req, res, fileId) {
try {
if (db_downloadable(fileId) == false) {
res.writeHead(404);
res.end(null);
return;
}
db_updateDownloadCredit(fileId);
var fileName = db_getFileName(fileId);
if (fileName == null) {
res.writeHead(404);
res.end(null);
return;
}
let data = fs.readFileSync(fileDir + "/" + fileId);
res.writeHead(200, {
"Content-Type": "application/octet-stream ",
"File-Name": fileName,
"Content-Length": data.length
});
res.end(data);
} catch (err) {
console.log(err);
res.writeHead(404);
res.end(null);
}
}
app.use(express.static(PATH));
// app.all()
app.post('/file/upload', function(req, res){
app.post("/file/upload", function (req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Methods", "POST");
res.header("Access-Control-Expose-Headers", "File-Name");
var fileSize = parseInt(req.get('Content-Length'))
var fileName = req.query.fileName;
var downloadCredit = parseInt(req.query.downloadCredit);
if(downloadCredit == -1)downloadCredit = 2147483647
if(!fileName){
res.statusCode = 500;
res.end("FileName Not Found");
}else if(fileSize > uploadMaxSize){
res.statusCode = 500;
res.end("Maximum size limit exceeded");
}
else{
uploadFile(req, res, fileName, downloadCredit);
}
var fileSize = parseInt(req.get("Content-Length"));
var fileName = req.query.fileName;
var downloadCredit = parseInt(req.query.downloadCredit);
if (downloadCredit == -1) downloadCredit = 2147483647;
if (!fileName) {
res.statusCode = 500;
res.end("FileName Not Found");
} else if (fileSize > uploadMaxSize) {
res.statusCode = 500;
res.end("Maximum size limit exceeded");
} else {
uploadFile(req, res, fileName, downloadCredit);
}
});
app.get('/file/download', function(req, res){
app.get("/file/download", function (req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Methods", "GET");
res.header("Access-Control-Expose-Headers", "File-Name");
var fileId = req.query.fileName;
if(!fileId){
res.statusCode = 500;
res.end("FileName Not Found");
}else{
downloadFile(req, res, fileId);
}
var fileId = req.query.fileName;
if (!fileId) {
res.statusCode = 500;
res.end("FileName Not Found");
} else {
downloadFile(req, res, fileId);
}
});
// 启动服务器
app.listen(PORT, HOST, function(){
// // 启动服务器
// app.listen(PORT, HOST, function(){
// console.log(app)
// // console.log(`miaoShare server listening on ${}`)
// });
const server = http.createServer(app);
const wss = new WebSocketServer({ server });
// 先实现最低限度功能,暂时不考虑身份认证
const groupsMap = {};
// 三天不活跃的群组将被清除
const LifeOfGroup = millisecondsOfOneDay * 3;
// const LifeOfGroup = 0;
db.exec(
`CREATE TABLE IF NOT EXISTS GROUPS (
gID CHAR(8) PRIMARY KEY UNIQUE NOT NULL,
verify TEXT NOT NULL,
activeTimestamp INT NOT NULL
);`)
const stmt_getExpiredGroups = db.prepare(`SELECT gID FROM GROUPS WHERE activeTimestamp < ? `);
const stmt_getGroupVerify = db.prepare(`SELECT verify FROM GROUPS WHERE gID = ? `)
const stmt_insertGROUPS = db.prepare(`INSERT INTO GROUPS (gID,verify, activeTimestamp) VALUES (?, ?, ?)`);
const stmt_updateGROUPS_ActiveTimestamp = db.prepare(`UPDATE GROUPS SET activeTimestamp=? WHERE gID=?`);
const stmt_deleteGroup = db.prepare(`DELETE FROM GROUPS WHERE gID=?`);
const generateStmt_insertMessage_map = {}
const updateGROUPS = () => {
// console.log(1)
try {
let gIDs = stmt_getExpiredGroups.all(parseInt(Date.now()) - LifeOfGroup);
for (let row of gIDs) {
let gID = row.gID
db.exec(`DROP TABLE IF EXISTS ${gID}`)
stmt_deleteGroup.run(gID)
}
} catch (e) {
console.log(e);
return;
}
};
updateGROUPS()
setInterval(updateGROUPS,1000*3600*3)
// 创建群组的数据库
function createGroupTableDB(gID,verify){
db.exec(
`CREATE TABLE IF NOT EXISTS ${gID} (
message TEXT NOT NULL,
type TEXT NOT NULL,
sender TEXT NOT NULL,
timestamp INT NOT NULL
);`)
stmt_insertGROUPS.run(gID,verify,parseInt(Date.now()))
}
// 插入信息
function insertMessageByGID(gID,msg,sender){
if(!generateStmt_insertMessage_map[gID]){
generateStmt_insertMessage_map[gID] = db.prepare(`INSERT INTO ${gID} (message,type,sender, timestamp) VALUES (?, ?, ?, ?)`);
}
generateStmt_insertMessage_map[gID].run(msg,'gMsg',sender,parseInt(Date.now()))
}
function updateActiveTimestamp(gID){
stmt_updateGROUPS_ActiveTimestamp.run(parseInt(Date.now()),gID)
}
function createGroup(verify) {
// 暂时不考虑id随机生成时重复的问题
let gID = generateId(true);
groupsMap[gID] = { verify, member: [], msgHistory: [] };
createGroupTableDB(gID,verify)
return gID;
}
function getGroupVerify(gID) {
if(!groupsMap[gID]){
let verify = stmt_getGroupVerify.get(gID).verify
// 暂时这么写
let msgHistory = db.prepare(`SELECT message,type FROM ${gID}`).all().map(v=>{
return {msg: v.message,type:v.type}
})
// console.log(msgHistory)
groupsMap[gID] = { verify, member: [], msgHistory};
}
return groupsMap[gID].verify;
}
// 正式加入群组
function joinGroup(socket, gID, callback) {
groupsMap[gID].member.push(socket);
updateActiveTimestamp(gID)
callback();
}
// 暂时这么写
function getHistory(gID) {
return groupsMap[gID].msgHistory;
}
function broadcastMsg(gID, msg) {
updateActiveTimestamp(gID)
insertMessageByGID(gID,msg,'NULL')
let bmsg = JSON.stringify({ type: "gMsg", gID, msg })
groupsMap[gID].msgHistory.push({ type: "gMsg", msg });
for (let socket of groupsMap[gID].member) {
socket.send(bmsg);
}
}
wss.on("connection", socket => {
console.log("新的连接已建立");
const no = wss.clients.size;
const joinedGroup = []
socket.on("message", message => {
try {
console.log(message.toString());
let msg = JSON.parse(message.toString());
if (msg.type) {
let type = msg.type;
// 创建群组
if (type === "create") {
socket.send(JSON.stringify({ type: "sMsg", type1: "create", gID: createGroup(msg.verify) }));
} else if (type === "join") {
// 加入群组——客端验证key
if (msg.type1 === "verify") {
// 已经加入则return
if(joinedGroup.includes(msg.gID)){
return
}
socket.send(JSON.stringify({ type: "sMsg", type1: "joinVerify", verify: getGroupVerify(msg.gID), gID: msg.gID }));
} else if (msg.type1 === "success") {
if(joinedGroup.includes(msg.gID)){
return
}
// 成功加入
joinGroup(socket, msg.gID, () => {
joinedGroup.push(msg.gID)
// 不考虑失败属于是
socket.send(JSON.stringify({ type: "sMsg",gID:msg.gID, type1: "history", history: JSON.stringify(getHistory(msg.gID)) }));
});
}
} else if (type === "message") {
let gID = msg.gID;
broadcastMsg(gID, msg.msg);
}
}
} catch (e) {
console.log(e);
}
});
socket.on("close", () => {
for(let gID of joinedGroup){
groupsMap[gID].member = groupsMap[gID].member.filter(v=>v!==socket)
}
console.log(`No.${no} exit.`);
console.log(groupsMap)
});
});
server.listen(PORT, HOST, () => {});