添加chat功能

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

No files matched your search

+13
View File
@@ -9,6 +9,7 @@
"version": "1.2.0", "version": "1.2.0",
"dependencies": { "dependencies": {
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"crypto-js": "^4.2.0",
"express": "^4.18.2", "express": "^4.18.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"js-base64": "^3.7.6", "js-base64": "^3.7.6",
@@ -21,6 +22,7 @@
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@vicons/ionicons5": "^0.12.0",
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^5.0.3",
"better-sqlite3": "^8.7.0", "better-sqlite3": "^8.7.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
@@ -652,6 +654,12 @@
"@types/lodash": "*" "@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": { "node_modules/@vitejs/plugin-vue": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.0.3.tgz",
@@ -1043,6 +1051,11 @@
"node": ">= 0.10" "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": { "node_modules/css-render": {
"version": "0.15.12", "version": "0.15.12",
"resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.12.tgz", "resolved": "https://registry.npmmirror.com/css-render/-/css-render-0.15.12.tgz",
+2
View File
@@ -12,6 +12,7 @@
}, },
"dependencies": { "dependencies": {
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"crypto-js": "^4.2.0",
"express": "^4.18.2", "express": "^4.18.2",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"js-base64": "^3.7.6", "js-base64": "^3.7.6",
@@ -24,6 +25,7 @@
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@vicons/ionicons5": "^0.12.0",
"@vitejs/plugin-vue": "^5.0.3", "@vitejs/plugin-vue": "^5.0.3",
"better-sqlite3": "^8.7.0", "better-sqlite3": "^8.7.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
+262 -80
View File
@@ -1,120 +1,132 @@
import express from 'express' import express from "express";
import cors from 'cors' import cors from "cors";
import Database from 'better-sqlite3'; import Database from "better-sqlite3";
import fs from "fs" import fs from "fs";
import { WebSocketServer } from "ws";
import http from "http";
// import Config from "./miaoShareConfig.json" // import Config from "./miaoShareConfig.json"
const HOST = "0.0.0.0";
const HOST = '0.0.0.0'
// const PORT = Config.PORT // const PORT = Config.PORT
const PORT = 17057 const PORT = 17057;
const fileDir = "./temp"; const fileDir = "./temp";
const dbPath = './miaoShare.db' const dbPath = "./miaoShare.db";
const PATH = "./dist"; const PATH = "./dist";
// 上传文件最大大小:200MB // 上传文件最大大小:200MB
const uploadMaxSize=200*1024*1024 const uploadMaxSize = 200 * 1024 * 1024;
// 一天的毫秒数 // 一天的毫秒数
const millisecondsOfTheDay = parseInt(24*3600*1000) const millisecondsOfOneDay = parseInt(24 * 3600 * 1000);
// 文件3天过期 // 文件3天过期
const LifeOfFile=millisecondsOfTheDay*3 const LifeOfFile = millisecondsOfOneDay * 3;
// const LifeOfFile=0 // const LifeOfFile=0
// 数据库相关 // 数据库相关
const db = new Database(dbPath, { verbose: null }); 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, id CHAR(8) PRIMARY KEY NOT NULL UNIQUE,
fileName TEXT NOT NULL, fileName TEXT NOT NULL,
timestamp INT NOT NULL, timestamp INT NOT NULL,
freeze tinyint(1) NOT NULL, freeze tinyint(1) NOT NULL,
downloadCredit INT NOT NULL downloadCredit INT NOT NULL
);`).run() );`
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`) ).run();
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`) const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`);
const stmt_getFreeze = db.prepare(`SELECT freeze FROM id2fileName WHERE id = ?`) const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`);
const stmt_getDownloadCredit = db.prepare(`SELECT downloadCredit FROM id2fileName WHERE id = ?`) const stmt_getFreeze = db.prepare(`SELECT freeze FROM id2fileName WHERE id = ?`);
const stmt_getFreezenIDs = db.prepare(`SELECT id FROM id2fileName WHERE freeze = 1`) const stmt_getDownloadCredit = db.prepare(`SELECT downloadCredit FROM id2fileName WHERE id = ?`);
const stmt_insertId2FileName = db.prepare(`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit) VALUES (?, ?, ?, ?, ?)`) const stmt_getFreezenIDs = db.prepare(`SELECT id FROM id2fileName WHERE freeze = 1`);
const stmt_deleteExpireData = db.prepare(`DELETE FROM id2fileName WHERE freeze=1`) const stmt_insertId2FileName = db.prepare(`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit) VALUES (?, ?, ?, ?, ?)`);
const stmt_freezeExpireData = db.prepare(`UPDATE id2fileName SET freeze=1 WHERE timestamp < ? OR downloadCredit < 1`) const stmt_deleteExpireData = db.prepare(`DELETE FROM id2fileName WHERE freeze=1`);
const stmt_updateDownloadCredit = db.prepare(`UPDATE id2fileName SET downloadCredit=downloadCredit-1 WHERE id = ?`) 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) => { const db_insert = (id, fileName, downloadCredit) => {
stmt_insertId2FileName.run(id,fileName,parseInt(Date.now()),0,downloadCredit) stmt_insertId2FileName.run(id, fileName, parseInt(Date.now()), 0, downloadCredit);
} };
const db_getFileName = (id)=>{ const db_getFileName = id => {
try { try {
if(stmt_getFreeze.get(id)['freeze'] == 1)return null if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
return stmt_getFileName.get(id)['fileName'] return stmt_getFileName.get(id)["fileName"];
} catch (e) { } catch (e) {
return null return null;
}
} }
};
// 检查是否可下载 // 检查是否可下载
const db_downloadable = (id)=>{ const db_downloadable = id => {
try { try {
// 懒 // 懒
if(stmt_getDownloadCredit.get(id)['downloadCredit'] < 1)return false if (stmt_getDownloadCredit.get(id)["downloadCredit"] < 1) return false;
if(stmt_getFreeze.get(id)['freeze'] == 1)return false if (stmt_getFreeze.get(id)["freeze"] == 1) return false;
return true return true;
} catch (e) { } catch (e) {
return false return false;
} }
} };
const db_updateDownloadCredit = (id)=>{ const db_updateDownloadCredit = id => {
try { try {
stmt_updateDownloadCredit.run(id) stmt_updateDownloadCredit.run(id);
return true return true;
} catch (e) { } catch (e) {
return false return false;
}
} }
};
const db_getTimestamp = (id)=>{ const db_getTimestamp = id => {
return stmt_getTimestamp.get(id)['timestamp'] return stmt_getTimestamp.get(id)["timestamp"];
} };
// 更新存储的文件,删除已经被冻结的文件以及相关记录,然后冻结过期文件 // 更新存储的文件,删除已经被冻结的文件以及相关记录,然后冻结过期文件
const updateDBAndFiles = () => { const updateDBAndFiles = () => {
try { try {
let rows = stmt_getFreezenIDs.all() let rows = stmt_getFreezenIDs.all();
stmt_deleteExpireData.run() stmt_deleteExpireData.run();
for (let row of rows) { for (let row of rows) {
let fileName = row['id'] let fileName = row["id"];
let path = fileDir + '/' + fileName let path = fileDir + "/" + fileName;
try { try {
fs.unlink(path,(e)=>{if(e)throw e}) fs.unlink(path, e => {
if (e) throw e;
});
} catch (e) { } catch (e) {
console.log(e) console.log(e);
continue continue;
} }
} }
stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile) stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile);
} catch (e) { } catch (e) {
console.log(e) console.log(e);
return return;
} }
} };
updateDBAndFiles() updateDBAndFiles();
// 3小时更新一次 // 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.use(cors());
app.options('*', cors()); app.options("*", cors());
var tempMap = {} // var tempMap = {}
if (!fs.existsSync(fileDir)) { if (!fs.existsSync(fileDir)) {
fs.mkdirSync(fileDir); fs.mkdirSync(fileDir);
} }
function generateId(){ function generateId(isGID) {
var returnStr = "", var returnStr = "",
range = 8, range = 8,
charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; charStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (var i = 0; i < range; i++) { 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); returnStr += charStr.substring(index, index + 1);
} }
return returnStr; return returnStr;
@@ -123,12 +135,14 @@ function generateId(){
// 上传文件接口 // 上传文件接口
function uploadFile(req, res, fileName, downloadCredit) { function uploadFile(req, res, fileName, downloadCredit) {
let id = generateId(); let id = generateId();
const fileStream = fs.createWriteStream(fileDir + '/' + id); const fileStream = fs.createWriteStream(fileDir + "/" + id);
db_insert(id,fileName,downloadCredit) db_insert(id, fileName, downloadCredit);
// tempMap[id] = fileName; // tempMap[id] = fileName;
// console.log(tempMap); // console.log(tempMap);
req.pipe(fileStream); req.pipe(fileStream);
res.on('data',(c)=>{console.log(c)}) res.on("data", c => {
console.log(c);
});
req.on("end", function () { req.on("end", function () {
fileStream.end(); fileStream.end();
res.statusCode = 200; res.statusCode = 200;
@@ -146,20 +160,20 @@ function downloadFile(req, res, fileId){
if (db_downloadable(fileId) == false) { if (db_downloadable(fileId) == false) {
res.writeHead(404); res.writeHead(404);
res.end(null); res.end(null);
return return;
} }
db_updateDownloadCredit(fileId) db_updateDownloadCredit(fileId);
var fileName = db_getFileName(fileId) var fileName = db_getFileName(fileId);
if (fileName == null) { if (fileName == null) {
res.writeHead(404); res.writeHead(404);
res.end(null); res.end(null);
return return;
} }
let data = fs.readFileSync(fileDir + '/' + fileId); let data = fs.readFileSync(fileDir + "/" + fileId);
res.writeHead(200, { res.writeHead(200, {
"Content-Type": "application/octet-stream ", "Content-Type": "application/octet-stream ",
"File-Name": fileName, "File-Name": fileName,
'Content-Length':data.length, "Content-Length": data.length
}); });
res.end(data); res.end(data);
} catch (err) { } catch (err) {
@@ -169,31 +183,29 @@ function downloadFile(req, res, fileId){
} }
} }
app.use(express.static(PATH)); app.use(express.static(PATH));
// app.all() // 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-Origin", "*");
res.header("Access-Control-Allow-Headers", "*"); res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Methods", "POST"); res.header("Access-Control-Allow-Methods", "POST");
res.header("Access-Control-Expose-Headers", "File-Name"); 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 fileName = req.query.fileName;
var downloadCredit = parseInt(req.query.downloadCredit); var downloadCredit = parseInt(req.query.downloadCredit);
if(downloadCredit == -1)downloadCredit = 2147483647 if (downloadCredit == -1) downloadCredit = 2147483647;
if (!fileName) { if (!fileName) {
res.statusCode = 500; res.statusCode = 500;
res.end("FileName Not Found"); res.end("FileName Not Found");
} else if (fileSize > uploadMaxSize) { } else if (fileSize > uploadMaxSize) {
res.statusCode = 500; res.statusCode = 500;
res.end("Maximum size limit exceeded"); res.end("Maximum size limit exceeded");
} } else {
else{
uploadFile(req, res, fileName, downloadCredit); 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-Origin", "*");
res.header("Access-Control-Allow-Headers", "*"); res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Methods", "GET"); 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
View File
@@ -1,8 +1,50 @@
<script setup> <script setup>
import uploadFileMiao from "./components/uploadFileMiao.vue"; import upload from "./views/uploadFileMiao.vue";
import downloadFileMiao from "./components/downloadFileMiao.vue"; import download from "./views/downloadFileMiao.vue";
import settingMiao from "./components/setting.vue"; import setting from "./views/setting.vue";
import {NNotificationProvider,NMessageProvider,NCard,NTabs,NTabPane} from 'naive-ui' 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> </script>
@@ -12,17 +54,35 @@ import {NNotificationProvider,NMessageProvider,NCard,NTabs,NTabPane} from 'naive
<n-notification-provider placement="bottom-right" :max="3"> <n-notification-provider placement="bottom-right" :max="3">
<n-message-provider> <n-message-provider>
<n-card content-style="padding: 0; margin:0;" id="theCard"> <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="上传文件"> <n-tab-pane name="上传文件">
<uploadFileMiao style /> <uploadFileMiao style />
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="下载文件"> <n-tab-pane name="下载文件">
<downloadFileMiao style /> <downloadFileMiao style />
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="miaoChat">
<chat style />
</n-tab-pane>
<n-tab-pane name="设置"> <n-tab-pane name="设置">
<settingMiao style="max-width:100%" /> <settingMiao style="max-width:100%" />
</n-tab-pane> </n-tab-pane>
</n-tabs> </n-tabs> -->
</n-card> </n-card>
</n-message-provider> </n-message-provider>
</n-notification-provider> </n-notification-provider>
@@ -39,9 +99,19 @@ import {NNotificationProvider,NMessageProvider,NCard,NTabs,NTabPane} from 'naive
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
#theCard { #theCard {
width: 50%; width: 40vw;
height: 80%; 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) { @media screen and (max-width:768px) {
#theCard { #theCard {
+9 -12
View File
@@ -18,14 +18,15 @@ const blob2Uint8Array = function (blob) {
}); });
}; };
const decrypt = (key, data) => { const decrypt = (key, data) => {
key = key.trim()
return blob2Uint8Array(data).then(data => { return blob2Uint8Array(data).then(data => {
// 无加密 // 无加密
if (key.length === 0) { if (key.length === 0) {
return data; return data;
} }
// SM4 // SM4
if (key.length >= 34) { if (key.length === 19) {
key = key.substring(3); key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
// console.log([...data]) // console.log([...data])
var decryptedBytes = new Uint8Array(sm4.decrypt([...data], key, { output: "array" })); var decryptedBytes = new Uint8Array(sm4.decrypt([...data], key, { output: "array" }));
return decryptedBytes; return decryptedBytes;
@@ -39,14 +40,17 @@ const decrypt = (key, data) => {
}); });
}; };
const decryptString = (key, str) => { const decryptString = (key, str) => {
key = key.trim()
console.log(key.length)
// 无加密 // 无加密
if (key.length === 0) { if (key.length === 0) {
return sm4.decrypt(str, "miaospringmiaospringmiaospringao"); // return sm4.decrypt(str, "miaospringmiaospringmiaospringao");
return Base64.decode(str)
} }
// SM4 // SM4
// console.log(key,key.length) // console.log(key,key.length)
if (key.length >= 34) { if (key.length === 19) {
key = key.substring(3); key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
return sm4.decrypt(str, key); return sm4.decrypt(str, key);
} }
// AES // AES
@@ -67,13 +71,6 @@ const downloadFromUint8Array = (data, fileName) => {
let blob = new Blob([uint8Array], { type: "text/plain" }); let blob = new Blob([uint8Array], { type: "text/plain" });
// 使用file-saver.js // 使用file-saver.js
saveAs(blob, fileName); 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 }; export { blob2Uint8Array, decrypt, decryptString, downloadFromUint8Array };
+15 -5
View File
@@ -21,13 +21,17 @@ const generateKey = function(setting) {
} }
} }
var returnStr = "", var returnStr = "",
length = setting.activeFuction === "AES128" ? 16 : 32 length = setting.activeFuction !== "AES256" ? 16 : 32
// charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
for (var i = 0; i < length; i++) { 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); returnStr += charMap.substring(index, index + 1);
} }
if(setting.activeFuction === "SM4"){
returnStr = 'SM4' + returnStr
}
// console.log(returnStr,returnStr.length)
return returnStr; return returnStr;
}; };
const blob2Uint8Array = function(blob) { const blob2Uint8Array = function(blob) {
@@ -61,12 +65,15 @@ const encrypt = (key, data, fileName, setting, message) => {
let encrypted_data = null let encrypted_data = null
if(setting.activeFuction === "不加密"){ if(setting.activeFuction === "不加密"){
encrypted_data = data encrypted_data = data
// AES
}else if(setting.activeFuction != "SM4"){ }else if(setting.activeFuction != "SM4"){
message.info(`开始加密${fileName}`); message.info(`开始加密${fileName}`);
encrypted_data = AES(key,data) encrypted_data = AES(key,data)
message.info(`${fileName}加密完成`); message.info(`${fileName}加密完成`);
// SM4
}else{ }else{
message.info(`开始加密${fileName}`); message.info(`开始加密${fileName}`);
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
encrypted_data = SM4(key,data) encrypted_data = SM4(key,data)
message.info(`${fileName}加密完成`); message.info(`${fileName}加密完成`);
} }
@@ -76,9 +83,10 @@ const encrypt = (key, data, fileName, setting, message) => {
// setting为pinia存储的setting。。。 // setting为pinia存储的setting。。。
const encryptString = (key, str, setting) => { const encryptString = (key, str, setting) => {
// console.log(key) // console.log(key,key.length)
if(setting.activeFuction === "不加密"){ if(setting.activeFuction === "不加密" || key === ''){
return sm4.encrypt(str, 'miaospringmiaospringmiaospringao') // return sm4.encrypt(str, 'miaospringmiaospringmiaospringao')
return Base64.encode(str)
}else if(setting.activeFuction !== "SM4"){ }else if(setting.activeFuction !== "SM4"){
key = aesjs.utils.utf8.toBytes(key); key = aesjs.utils.utf8.toBytes(key);
var data = aesjs.utils.utf8.toBytes(str); var data = aesjs.utils.utf8.toBytes(str);
@@ -86,6 +94,8 @@ const encryptString = (key, str, setting) => {
var encryptedBytes = aesCtr.encrypt(data); var encryptedBytes = aesCtr.encrypt(data);
return Base64.encode(encryptedBytes); return Base64.encode(encryptedBytes);
}else{ }else{
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
// console.log(key)
return sm4.encrypt(str, key) return sm4.encrypt(str, key)
} }
}; };
+2 -2
View File
@@ -3,7 +3,7 @@ import 'vfonts/Lato.css'
import { createApp } from "vue"; import { createApp } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; // import router from "./router";
// import naive from "naive-ui"; // import naive from "naive-ui";
import { createPinia } from "pinia"; import { createPinia } from "pinia";
@@ -14,7 +14,7 @@ import { createPinia } from "pinia";
const pinia = createPinia() const pinia = createPinia()
const app = createApp(App); const app = createApp(App);
app.use(router); // app.use(router);
app.use(pinia); app.use(pinia);
// app.use(naive); // app.use(naive);
+42 -14
View File
@@ -1,22 +1,50 @@
// 未使用
import { createRouter, createWebHistory } from 'vue-router' 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({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
// { {
// path: '/', path: '/',
// name: 'home', redirect: '/upload',
// component: HomeView },
// }, {
// { path: '/upload',
// path: '/about', name: 'upload',
// name: 'about', component: uploadFileMiao,
// // route level code-splitting meta: {
// // this generates a separate chunk (About.[hash].js) for this route keepAlive: true,
// // which is lazy-loaded when the route is visited. }
// component: () => import('../views/AboutView.vue') },
// } {
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,
}
},
] ]
}) })
+139
View File
@@ -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" :max-height="250"
:bordered="false" :bordered="false"
:single-line="false" :single-line="false"
style="user-select: none;"
/> />
</div> </div>
</template> </template>
@@ -2,7 +2,7 @@
<div> <div>
<n-list hoverable> <n-list hoverable>
<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 /> <br />
<!-- <br /> --> <!-- <br /> -->
<!-- <br /> --> <!-- <br /> -->
@@ -16,7 +16,7 @@
</n-flex> </n-flex>
</n-list-item> </n-list-item>
<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 /> <br />
<n-flex justify="end"> <n-flex justify="end">
<span v-for="k in keyButtons" style="margin:5px"> <span v-for="k in keyButtons" style="margin:5px">
@@ -11,8 +11,8 @@
<!-- <div style="margin-bottom: 12px"> <!-- <div style="margin-bottom: 12px">
<n-icon size="48" :depth="3"></n-icon> <n-icon size="48" :depth="3"></n-icon>
</div>--> </div>-->
<n-text style="font-size: 16px">点击此处或者拖动文件到该区域来上传</n-text> <n-text style="font-size: 16px;user-select: none;">点击此处或者拖动文件到该区域来上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0"> <n-p depth="3" style="margin: 8px 0 0 0;user-select: none;">
最大支持200MB大小文件,其实只是懒得做分片上传而已 最大支持200MB大小文件,其实只是懒得做分片上传而已
<br />虽然理论上没有人有能力审查你上传的东西 <br />虽然理论上没有人有能力审查你上传的东西
<br />反正没人用写这么多干什么。。。 <br />反正没人用写这么多干什么。。。
@@ -31,6 +31,7 @@
:max-height="250" :max-height="250"
:bordered="false" :bordered="false"
:single-line="false" :single-line="false"
style="user-select: none;"
/> />
</div> </div>
</template> </template>
@@ -208,9 +209,9 @@ const customRequest = ({
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
let id = xhr.responseText; let id = xhr.responseText;
let code = id + key let code = id + key
if(setting.activeFuction === "SM4"){ // if(setting.activeFuction === "SM4"){
code = id + 'SM4' + key // code = id + 'SM4' + key
} // }
file_Code_Map.value.push({ file_Code_Map.value.push({
code: code, code: code,
fileName: fileName fileName: fileName
+1 -1
View File
@@ -14,7 +14,7 @@ export default defineConfig({
} }
}, },
server:{ server:{
host:'0.0.0.0', // host:'0.0.0.0',
port:'17058', port:'17058',
open:true, open:true,
}, },