添加chat功能
This commit is contained in:
1 parent
08e7820f37
commit
127f58bd36
13 files changed
+572
-129
No files matched your search
Generated
+13
@@ -9,6 +9,7 @@
|
||||
"version": "1.2.0",
|
||||
"dependencies": {
|
||||
"aes-js": "^3.1.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"express": "^4.18.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"js-base64": "^3.7.6",
|
||||
@@ -21,6 +22,7 @@
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"better-sqlite3": "^8.7.0",
|
||||
"concurrently": "^8.2.2",
|
||||
@@ -652,6 +654,12 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vicons/ionicons5": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.12.0.tgz",
|
||||
"integrity": "sha512-Iy1EUVRpX0WWxeu1VIReR1zsZLMc4fqpt223czR+Rpnrwu7pt46nbnC2ycO7ItI/uqDLJxnbcMC7FujKs9IfFA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz",
|
||||
@@ -1043,6 +1051,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="
|
||||
},
|
||||
"node_modules/css-render": {
|
||||
"version": "0.15.12",
|
||||
"resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.12.tgz",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"aes-js": "^3.1.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"express": "^4.18.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"js-base64": "^3.7.6",
|
||||
@@ -24,6 +25,7 @@
|
||||
"ws": "^8.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vicons/ionicons5": "^0.12.0",
|
||||
"@vitejs/plugin-vue": "^5.0.3",
|
||||
"better-sqlite3": "^8.7.0",
|
||||
"concurrently": "^8.2.2",
|
||||
|
||||
@@ -1,120 +1,132 @@
|
||||
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)=>{
|
||||
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']
|
||||
if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
|
||||
return stmt_getFileName.get(id)["fileName"];
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
// 检查是否可下载
|
||||
const db_downloadable = (id)=>{
|
||||
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
|
||||
if (stmt_getDownloadCredit.get(id)["downloadCredit"] < 1) return false;
|
||||
if (stmt_getFreeze.get(id)["freeze"] == 1) return false;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const db_updateDownloadCredit = (id)=>{
|
||||
};
|
||||
const db_updateDownloadCredit = id => {
|
||||
try {
|
||||
stmt_updateDownloadCredit.run(id)
|
||||
return true
|
||||
stmt_updateDownloadCredit.run(id);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
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()
|
||||
let rows = stmt_getFreezenIDs.all();
|
||||
stmt_deleteExpireData.run();
|
||||
for (let row of rows) {
|
||||
let fileName = row['id']
|
||||
let path = fileDir + '/' + fileName
|
||||
let fileName = row["id"];
|
||||
let path = fileDir + "/" + fileName;
|
||||
try {
|
||||
fs.unlink(path,(e)=>{if(e)throw e})
|
||||
fs.unlink(path, e => {
|
||||
if (e) throw e;
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
continue
|
||||
console.log(e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile)
|
||||
stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return
|
||||
console.log(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateDBAndFiles()
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
function generateId(){
|
||||
function generateId(isGID) {
|
||||
var returnStr = "",
|
||||
range = 8,
|
||||
charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
charStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
for (var i = 0; i < range; i++) {
|
||||
var index = Math.round(Math.random() * 35);
|
||||
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;
|
||||
@@ -123,12 +135,14 @@ function generateId(){
|
||||
// 上传文件接口
|
||||
function uploadFile(req, res, fileName, downloadCredit) {
|
||||
let id = generateId();
|
||||
const fileStream = fs.createWriteStream(fileDir + '/' + id);
|
||||
db_insert(id,fileName,downloadCredit)
|
||||
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)})
|
||||
res.on("data", c => {
|
||||
console.log(c);
|
||||
});
|
||||
req.on("end", function () {
|
||||
fileStream.end();
|
||||
res.statusCode = 200;
|
||||
@@ -146,20 +160,20 @@ function downloadFile(req, res, fileId){
|
||||
if (db_downloadable(fileId) == false) {
|
||||
res.writeHead(404);
|
||||
res.end(null);
|
||||
return
|
||||
return;
|
||||
}
|
||||
db_updateDownloadCredit(fileId)
|
||||
var fileName = db_getFileName(fileId)
|
||||
db_updateDownloadCredit(fileId);
|
||||
var fileName = db_getFileName(fileId);
|
||||
if (fileName == null) {
|
||||
res.writeHead(404);
|
||||
res.end(null);
|
||||
return
|
||||
return;
|
||||
}
|
||||
let data = fs.readFileSync(fileDir + '/' + fileId);
|
||||
let data = fs.readFileSync(fileDir + "/" + fileId);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "application/octet-stream ",
|
||||
"File-Name": fileName,
|
||||
'Content-Length':data.length,
|
||||
"Content-Length": data.length
|
||||
});
|
||||
res.end(data);
|
||||
} catch (err) {
|
||||
@@ -169,31 +183,29 @@ function downloadFile(req, res, fileId){
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 fileSize = parseInt(req.get("Content-Length"));
|
||||
var fileName = req.query.fileName;
|
||||
var downloadCredit = parseInt(req.query.downloadCredit);
|
||||
if(downloadCredit == -1)downloadCredit = 2147483647
|
||||
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{
|
||||
} 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");
|
||||
@@ -207,7 +219,177 @@ app.get('/file/download', function(req, res){
|
||||
}
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
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, () => {});
|
||||
+78
-8
@@ -1,8 +1,50 @@
|
||||
<script setup>
|
||||
import uploadFileMiao from "./components/uploadFileMiao.vue";
|
||||
import downloadFileMiao from "./components/downloadFileMiao.vue";
|
||||
import settingMiao from "./components/setting.vue";
|
||||
import {NNotificationProvider,NMessageProvider,NCard,NTabs,NTabPane} from 'naive-ui'
|
||||
import upload from "./views/uploadFileMiao.vue";
|
||||
import download from "./views/downloadFileMiao.vue";
|
||||
import setting from "./views/setting.vue";
|
||||
import chat from "./views/chat.vue";
|
||||
import { NNotificationProvider, NMessageProvider, NCard, NMenu, NIcon } from 'naive-ui'
|
||||
import { ref, watch, h ,shallowRef} from "vue";
|
||||
// import { RouterView, useRouter } from "vue-router";
|
||||
import {
|
||||
ArrowUpCircleOutline as uploadIcon,
|
||||
ArrowDownCircleOutline as downloadIcon,
|
||||
ChatboxEllipsesOutline as chatIcon,
|
||||
SettingsOutline as settingIcon
|
||||
|
||||
} from '@vicons/ionicons5'
|
||||
|
||||
const activePage = ref('upload')
|
||||
const isComponent = shallowRef(upload)
|
||||
watch(activePage, () => {
|
||||
isComponent.value={upload,download,setting,chat}[activePage.value]
|
||||
})
|
||||
function renderIcon(icon) {
|
||||
return () => h(NIcon, null, { default: () => h(icon) })
|
||||
}
|
||||
|
||||
const menuOptions = [
|
||||
{
|
||||
label: '上传',
|
||||
key: 'upload',
|
||||
icon: renderIcon(uploadIcon),
|
||||
},
|
||||
{
|
||||
label: '下载',
|
||||
key: 'download',
|
||||
icon: renderIcon(downloadIcon),
|
||||
},
|
||||
{
|
||||
label: 'Chat',
|
||||
key: 'chat',
|
||||
icon: renderIcon(chatIcon),
|
||||
},
|
||||
{
|
||||
label: '设置',
|
||||
key: 'setting',
|
||||
icon: renderIcon(settingIcon),
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
</script>
|
||||
@@ -12,17 +54,35 @@ import {NNotificationProvider,NMessageProvider,NCard,NTabs,NTabPane} from 'naive
|
||||
<n-notification-provider placement="bottom-right" :max="3">
|
||||
<n-message-provider>
|
||||
<n-card content-style="padding: 0; margin:0;" id="theCard">
|
||||
<n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;">
|
||||
<div id="menu">
|
||||
<n-menu mode="horizontal" :responsive="true" v-model:value="activePage" :options="menuOptions" :icon-size="26"></n-menu>
|
||||
</div>
|
||||
|
||||
<div style="padding: 20px;">
|
||||
<KeepAlive>
|
||||
<component :is="isComponent"></component>
|
||||
</KeepAlive>
|
||||
<!-- <router-view v-slot="{ Component }">
|
||||
<keep-alive :include=['uploadFileMiao']>
|
||||
<component :is="Component" :key="$route.fullPath"/>
|
||||
</keep-alive>
|
||||
</router-view> -->
|
||||
</div>
|
||||
<!-- <n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;"
|
||||
display-directive="show">
|
||||
<n-tab-pane name="上传文件">
|
||||
<uploadFileMiao style />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="下载文件">
|
||||
<downloadFileMiao style />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="miaoChat">
|
||||
<chat style />
|
||||
</n-tab-pane>
|
||||
<n-tab-pane name="设置">
|
||||
<settingMiao style="max-width:100%" />
|
||||
</n-tab-pane>
|
||||
</n-tabs>
|
||||
</n-tabs> -->
|
||||
</n-card>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
@@ -39,9 +99,19 @@ import {NNotificationProvider,NMessageProvider,NCard,NTabs,NTabPane} from 'naive
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#theCard {
|
||||
width: 50%;
|
||||
height: 80%;
|
||||
width: 40vw;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
#menu{
|
||||
width: 100%;
|
||||
height: auto;
|
||||
/* background-color: blue; */
|
||||
padding-top: 5px;
|
||||
border-bottom: 1px solid rgb(60, 60, 60, 0.1);
|
||||
user-select: none
|
||||
}
|
||||
@media screen and (max-width:768px) {
|
||||
#theCard {
|
||||
|
||||
+9
-12
@@ -18,14 +18,15 @@ const blob2Uint8Array = function (blob) {
|
||||
});
|
||||
};
|
||||
const decrypt = (key, data) => {
|
||||
key = key.trim()
|
||||
return blob2Uint8Array(data).then(data => {
|
||||
// 无加密
|
||||
if (key.length === 0) {
|
||||
return data;
|
||||
}
|
||||
// SM4
|
||||
if (key.length >= 34) {
|
||||
key = key.substring(3);
|
||||
if (key.length === 19) {
|
||||
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
|
||||
// console.log([...data])
|
||||
var decryptedBytes = new Uint8Array(sm4.decrypt([...data], key, { output: "array" }));
|
||||
return decryptedBytes;
|
||||
@@ -39,14 +40,17 @@ const decrypt = (key, data) => {
|
||||
});
|
||||
};
|
||||
const decryptString = (key, str) => {
|
||||
key = key.trim()
|
||||
console.log(key.length)
|
||||
// 无加密
|
||||
if (key.length === 0) {
|
||||
return sm4.decrypt(str, "miaospringmiaospringmiaospringao");
|
||||
// return sm4.decrypt(str, "miaospringmiaospringmiaospringao");
|
||||
return Base64.decode(str)
|
||||
}
|
||||
// SM4
|
||||
// console.log(key,key.length)
|
||||
if (key.length >= 34) {
|
||||
key = key.substring(3);
|
||||
if (key.length === 19) {
|
||||
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
|
||||
return sm4.decrypt(str, key);
|
||||
}
|
||||
// AES
|
||||
@@ -67,13 +71,6 @@ const downloadFromUint8Array = (data, fileName) => {
|
||||
let blob = new Blob([uint8Array], { type: "text/plain" });
|
||||
// 使用file-saver.js
|
||||
saveAs(blob, fileName);
|
||||
|
||||
//let url = URL.createObjectURL(blob);
|
||||
//let a = document.createElement("a");
|
||||
//a.href = url;
|
||||
//a.download = fileName;
|
||||
//a.click();
|
||||
//URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
export { blob2Uint8Array, decrypt, decryptString, downloadFromUint8Array };
|
||||
+15
-5
@@ -21,13 +21,17 @@ const generateKey = function(setting) {
|
||||
}
|
||||
}
|
||||
var returnStr = "",
|
||||
length = setting.activeFuction === "AES128" ? 16 : 32
|
||||
length = setting.activeFuction !== "AES256" ? 16 : 32
|
||||
// charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var index = Math.round(Math.random() * charMap.length);
|
||||
var index = parseInt(Math.random() * charMap.length);
|
||||
returnStr += charMap.substring(index, index + 1);
|
||||
}
|
||||
if(setting.activeFuction === "SM4"){
|
||||
returnStr = 'SM4' + returnStr
|
||||
}
|
||||
// console.log(returnStr,returnStr.length)
|
||||
return returnStr;
|
||||
};
|
||||
const blob2Uint8Array = function(blob) {
|
||||
@@ -61,12 +65,15 @@ const encrypt = (key, data, fileName, setting, message) => {
|
||||
let encrypted_data = null
|
||||
if(setting.activeFuction === "不加密"){
|
||||
encrypted_data = data
|
||||
// AES
|
||||
}else if(setting.activeFuction != "SM4"){
|
||||
message.info(`开始加密${fileName}`);
|
||||
encrypted_data = AES(key,data)
|
||||
message.info(`${fileName}加密完成`);
|
||||
// SM4
|
||||
}else{
|
||||
message.info(`开始加密${fileName}`);
|
||||
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
|
||||
encrypted_data = SM4(key,data)
|
||||
message.info(`${fileName}加密完成`);
|
||||
}
|
||||
@@ -76,9 +83,10 @@ const encrypt = (key, data, fileName, setting, message) => {
|
||||
|
||||
// setting为pinia存储的setting。。。
|
||||
const encryptString = (key, str, setting) => {
|
||||
// console.log(key)
|
||||
if(setting.activeFuction === "不加密"){
|
||||
return sm4.encrypt(str, 'miaospringmiaospringmiaospringao')
|
||||
// console.log(key,key.length)
|
||||
if(setting.activeFuction === "不加密" || key === ''){
|
||||
// return sm4.encrypt(str, 'miaospringmiaospringmiaospringao')
|
||||
return Base64.encode(str)
|
||||
}else if(setting.activeFuction !== "SM4"){
|
||||
key = aesjs.utils.utf8.toBytes(key);
|
||||
var data = aesjs.utils.utf8.toBytes(str);
|
||||
@@ -86,6 +94,8 @@ const encryptString = (key, str, setting) => {
|
||||
var encryptedBytes = aesCtr.encrypt(data);
|
||||
return Base64.encode(encryptedBytes);
|
||||
}else{
|
||||
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
|
||||
// console.log(key)
|
||||
return sm4.encrypt(str, key)
|
||||
}
|
||||
};
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import 'vfonts/Lato.css'
|
||||
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
// import router from "./router";
|
||||
// import naive from "naive-ui";
|
||||
import { createPinia } from "pinia";
|
||||
|
||||
@@ -14,7 +14,7 @@ import { createPinia } from "pinia";
|
||||
const pinia = createPinia()
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
// app.use(router);
|
||||
app.use(pinia);
|
||||
// app.use(naive);
|
||||
|
||||
|
||||
+42
-14
@@ -1,22 +1,50 @@
|
||||
// 未使用
|
||||
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
// import HomeView from '../views/HomeView.vue'
|
||||
import uploadFileMiao from "../views/uploadFileMiao.vue";
|
||||
import downloadFileMiao from "../views/downloadFileMiao.vue";
|
||||
import settingMiao from "../views/setting.vue";
|
||||
import chat from "../views/chat.vue";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
// {
|
||||
// path: '/',
|
||||
// name: 'home',
|
||||
// component: HomeView
|
||||
// },
|
||||
// {
|
||||
// path: '/about',
|
||||
// name: 'about',
|
||||
// // route level code-splitting
|
||||
// // this generates a separate chunk (About.[hash].js) for this route
|
||||
// // which is lazy-loaded when the route is visited.
|
||||
// component: () => import('../views/AboutView.vue')
|
||||
// }
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/upload',
|
||||
},
|
||||
{
|
||||
path: '/upload',
|
||||
name: 'upload',
|
||||
component: uploadFileMiao,
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/download',
|
||||
name: 'download',
|
||||
component: downloadFileMiao,
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/setting',
|
||||
name: 'setting',
|
||||
component: settingMiao,
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/chat',
|
||||
name: 'chat',
|
||||
component: chat,
|
||||
meta: {
|
||||
keepAlive: true,
|
||||
}
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<n-button @click="createGroup">
|
||||
createTest
|
||||
</n-button>
|
||||
<n-text> createTest gID:{{ gID }}<br />key:{{ key }}<br />code:{{ gID+key }}</n-text>
|
||||
<n-input
|
||||
v-model:value="codeInput"
|
||||
type="textarea"
|
||||
placeholder="测试加入群组"
|
||||
/>
|
||||
<n-button @click="joinGroup(codeInput)">
|
||||
joinTest
|
||||
</n-button>
|
||||
<n-input
|
||||
v-model:value="msg"
|
||||
type="textarea"
|
||||
placeholder="测试发送信息"
|
||||
/>
|
||||
<n-button @click="sendMsg(codeInput.substring(0,8),msg)">
|
||||
sendMsgTest
|
||||
</n-button>
|
||||
<n-button @click="data.length = 0">
|
||||
clearMsg
|
||||
</n-button>
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="data"
|
||||
:row-props="null"
|
||||
:max-height="250"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue'
|
||||
import {NText,NDataTable,NButton,NInput} from 'naive-ui'
|
||||
import { useSettingStore } from "@/pinia/store";
|
||||
import { generateKey,encryptString } from '@/func/upload';
|
||||
import { decryptString } from '@/func/download';
|
||||
|
||||
|
||||
|
||||
const setting = useSettingStore();
|
||||
const serverPort = setting.serverPort;
|
||||
const baseUrl = 'ws' + "://" + location.hostname + ":" + serverPort;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "from",
|
||||
key: "from"
|
||||
},
|
||||
{
|
||||
title: "message",
|
||||
key: "msg"
|
||||
},
|
||||
];
|
||||
const data = ref([])
|
||||
const gID = ref('')
|
||||
const codeInput = ref('')
|
||||
const key = ref('')
|
||||
const msg = ref('')
|
||||
const joinedGroup = []
|
||||
|
||||
|
||||
const gID2key = {}
|
||||
|
||||
|
||||
const socket = new WebSocket(baseUrl);
|
||||
socket.addEventListener("open", (event) => {
|
||||
// socket.send("");
|
||||
});
|
||||
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
const msg = JSON.parse(event.data)
|
||||
data.value.push({from:'server',msg:event.data})
|
||||
if(msg.type){
|
||||
let type = msg.type
|
||||
if(type === 'sMsg'){
|
||||
let type1 = msg.type1
|
||||
if(type1 === 'create'){
|
||||
gID.value=msg.gID
|
||||
}else if(type1 === 'joinVerify'){
|
||||
let gID = msg.gID
|
||||
let key = gID2key[gID]
|
||||
console.log(gID2key,gID,key)
|
||||
let res = decryptString(key,msg.verify)
|
||||
console.log(res,key,msg.verify)
|
||||
if(res === 'miaoSpring'){
|
||||
socket.send(JSON.stringify({type:'join',type1:'success',gID}))
|
||||
joinedGroup.push(msg.gID)
|
||||
}
|
||||
}else if(type1 === 'history'){
|
||||
let gID = msg.gID
|
||||
let key = gID2key[gID]
|
||||
for(let m of JSON.parse(msg.history)){
|
||||
data.value.push({from:'decrypt',msg:decryptString(key,m.msg)})
|
||||
}
|
||||
}
|
||||
}else if(type === 'gMsg'){
|
||||
let gID = msg.gID
|
||||
let key = gID2key[gID]
|
||||
data.value.push({from:'decrypt',msg:decryptString(key,msg.msg)})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function createGroup(){
|
||||
let keyt = generateKey(setting)
|
||||
key.value = keyt
|
||||
socket.send(JSON.stringify({type:'create',verify:encryptString(keyt,'miaoSpring',setting)}))
|
||||
}
|
||||
|
||||
function joinGroup(code){
|
||||
var gID = code.substring(0, 8);
|
||||
var key = code.substring(8);
|
||||
if(joinedGroup.includes(gID)){
|
||||
return
|
||||
}
|
||||
gID2key[gID] = key
|
||||
joinGroup_Verify(gID)
|
||||
}
|
||||
|
||||
function joinGroup_Verify(gID){
|
||||
socket.send(JSON.stringify({type:'join',type1:'verify',gID}))
|
||||
}
|
||||
|
||||
function sendMsg(gID,msg){
|
||||
let key = gID2key[gID]
|
||||
msg = encryptString(key,msg,setting)
|
||||
socket.send(JSON.stringify({type:'message',msg,gID}))
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -22,6 +22,7 @@
|
||||
:max-height="250"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
style="user-select: none;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
<n-list hoverable>
|
||||
<n-list-item>
|
||||
<p style="font-size:larger;position:absolute;left:5px;">选择加密算法:</p>
|
||||
<p style="font-size:larger;position:absolute;left:5px;user-select: none;">选择加密算法:</p>
|
||||
<br />
|
||||
<!-- <br /> -->
|
||||
<!-- <br /> -->
|
||||
@@ -16,7 +16,7 @@
|
||||
</n-flex>
|
||||
</n-list-item>
|
||||
<n-list-item>
|
||||
<p style="font-size:larger;position:absolute;left:5px;">选择密钥组成:</p>
|
||||
<p style="font-size:larger;position:absolute;left:5px;user-select: none;">选择密钥组成:</p>
|
||||
<br />
|
||||
<n-flex justify="end">
|
||||
<span v-for="k in keyButtons" style="margin:5px">
|
||||
@@ -11,8 +11,8 @@
|
||||
<!-- <div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3"></n-icon>
|
||||
</div>-->
|
||||
<n-text style="font-size: 16px">点击此处或者拖动文件到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">
|
||||
<n-text style="font-size: 16px;user-select: none;">点击此处或者拖动文件到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0;user-select: none;">
|
||||
最大支持200MB大小文件,其实只是懒得做分片上传而已
|
||||
<br />虽然理论上没有人有能力审查你上传的东西
|
||||
<br />反正没人用写这么多干什么。。。
|
||||
@@ -31,6 +31,7 @@
|
||||
:max-height="250"
|
||||
:bordered="false"
|
||||
:single-line="false"
|
||||
style="user-select: none;"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -208,9 +209,9 @@ const customRequest = ({
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
let id = xhr.responseText;
|
||||
let code = id + key
|
||||
if(setting.activeFuction === "SM4"){
|
||||
code = id + 'SM4' + key
|
||||
}
|
||||
// if(setting.activeFuction === "SM4"){
|
||||
// code = id + 'SM4' + key
|
||||
// }
|
||||
file_Code_Map.value.push({
|
||||
code: code,
|
||||
fileName: fileName
|
||||
+1
-1
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
server:{
|
||||
host:'0.0.0.0',
|
||||
// host:'0.0.0.0',
|
||||
port:'17058',
|
||||
open:true,
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user