Compare commits

..
11 Commits
24 changed files with 3572 additions and 210 deletions

No files matched your search

+5
View File
@@ -32,3 +32,8 @@ coverage
# files created with miaoShare service # files created with miaoShare service
temp temp
miaoShare.db miaoShare.db
addUser.js
removeUser.js
# JWT SK
*.key
+11 -1
View File
@@ -1,3 +1,13 @@
{ {
"PORT":17057 "PORT":17057,
"userGroupSetting":{
"admin":{
"cloudCapacity":2147483648
},
"guest":{
"cloudCapacity":2147483648
}
},
"enableGuest":false,
"tokenExpiresIn":168
} }
+130 -12
View File
@@ -12,7 +12,9 @@
"crypto-js": "^4.2.0", "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.7",
"jsonwebtoken": "^9.0.2",
"node-cron": "^3.0.3",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"sm-crypto": "^0.3.13", "sm-crypto": "^0.3.13",
"uuid": "^9.0.1", "uuid": "^9.0.1",
@@ -24,12 +26,13 @@
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@vicons/fluent": "^0.12.0",
"@vicons/ionicons5": "^0.12.0", "@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",
"cors": "^2.8.5", "cors": "^2.8.5",
"naive-ui": "^2.37.3", "naive-ui": "^2.38.1",
"vfonts": "^0.0.3", "vfonts": "^0.0.3",
"vite": "^5.0.11" "vite": "^5.0.11"
} }
@@ -656,6 +659,12 @@
"@types/lodash": "*" "@types/lodash": "*"
} }
}, },
"node_modules/@vicons/fluent": {
"version": "0.12.0",
"resolved": "https://registry.npmmirror.com/@vicons/fluent/-/fluent-0.12.0.tgz",
"integrity": "sha512-ATCiqPuiJ6RI5GBlD3BIpZ9Xw4MsCA4RpI5oR6MCti4quS4mX1Gp6N74FCzw7lgOj+80rV4HMKhZTVInwimpVQ==",
"dev": true
},
"node_modules/@vicons/ionicons5": { "node_modules/@vicons/ionicons5": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.12.0.tgz", "resolved": "https://registry.npmmirror.com/@vicons/ionicons5/-/ionicons5-0.12.0.tgz",
@@ -889,6 +898,11 @@
"ieee754": "^1.1.13" "ieee754": "^1.1.13"
} }
}, },
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/bytes": { "node_modules/bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz",
@@ -1174,6 +1188,14 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz",
@@ -1568,15 +1590,60 @@
} }
}, },
"node_modules/js-base64": { "node_modules/js-base64": {
"version": "3.7.6", "version": "3.7.7",
"resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.6.tgz", "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.7.tgz",
"integrity": "sha512-NPrWuHFxFUknr1KqJRDgUQPexQF0uIJWjeT+2KjEePhitQxQEx5EJBG1lVn5/hc8aLycTpXrDOgPQ6Zq+EDiTA==" "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
}, },
"node_modules/jsbn": { "node_modules/jsbn": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/jsbn/-/jsbn-1.1.0.tgz",
"integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="
}, },
"node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmmirror.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmmirror.com/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
@@ -1589,11 +1656,45 @@
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"dev": true "dev": true
}, },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmmirror.com/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmmirror.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": { "dependencies": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
}, },
@@ -1690,9 +1791,9 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
}, },
"node_modules/naive-ui": { "node_modules/naive-ui": {
"version": "2.37.3", "version": "2.38.1",
"resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.37.3.tgz", "resolved": "https://registry.npmmirror.com/naive-ui/-/naive-ui-2.38.1.tgz",
"integrity": "sha512-aUkHFXVIluSi8Me+npbcsdv1NYhVMj5t9YaruoCESlqmfqspj+R2QHEVXkTtUI1kQwVrABMCtAGq/wountqjZA==", "integrity": "sha512-AnU1FQ7K/CbhguAX++V4kCFjk7h7RvWt4nvZPRjORMpq+fUIlzD+EcQ5Cv1VqDloNF8+eMv4Akc2Ogacc9S+5A==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@css-render/plugin-bem": "^0.15.12", "@css-render/plugin-bem": "^0.15.12",
@@ -1756,6 +1857,25 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/node-cron": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/node-cron/-/node-cron-3.0.3.tgz",
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
"dependencies": {
"uuid": "8.3.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/node-cron/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
@@ -2056,7 +2176,6 @@
"version": "7.6.0", "version": "7.6.0",
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.0.tgz", "resolved": "https://registry.npmmirror.com/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
}, },
@@ -2581,8 +2700,7 @@
"node_modules/yallist": { "node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
"dev": true
}, },
"node_modules/yargs": { "node_modules/yargs": {
"version": "17.7.2", "version": "17.7.2",
+6 -2
View File
@@ -16,7 +16,9 @@
"crypto-js": "^4.2.0", "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.7",
"jsonwebtoken": "^9.0.2",
"node-cron": "^3.0.3",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"sm-crypto": "^0.3.13", "sm-crypto": "^0.3.13",
"uuid": "^9.0.1", "uuid": "^9.0.1",
@@ -25,15 +27,17 @@
"vue-clipboard3": "^2.0.0", "vue-clipboard3": "^2.0.0",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"websocket-heartbeat-js": "^1.1.3", "websocket-heartbeat-js": "^1.1.3",
"workerpool": "^9.1.3",
"ws": "^8.16.0" "ws": "^8.16.0"
}, },
"devDependencies": { "devDependencies": {
"@vicons/fluent": "^0.12.0",
"@vicons/ionicons5": "^0.12.0", "@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",
"cors": "^2.8.5", "cors": "^2.8.5",
"naive-ui": "^2.37.3", "naive-ui": "^2.38.1",
"vfonts": "^0.0.3", "vfonts": "^0.0.3",
"vite": "^5.0.11" "vite": "^5.0.11"
} }
+2196
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

+242 -48
View File
@@ -1,3 +1,7 @@
/*
仅作为临时测试使用
后续对这个进行拆分
*/
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";
@@ -5,15 +9,24 @@ import fs from "fs";
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import http from "http"; import http from "http";
import { v4 as uuid4 } from "uuid"; import { v4 as uuid4 } from "uuid";
// import Config from "./miaoShareConfig.json" import jwt from "jsonwebtoken";
import sha256 from "crypto-js/sha256.js";
import Config from "./miaoShareConfig.json" assert { type: "json" };
import { verify } from "crypto";
// function loadJSON(path){
// return JSON.parse(fs.readFileSync(path).toString());
// }
// const Config = loadJSON("./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";
const JWT_SK = fs.readFileSync("JWT_SK.key").toString();
// 上传文件最大大小:200MB // 上传文件最大大小:200MB
const uploadMaxSize = 200 * 1024 * 1024; const uploadMaxSize = 200 * 1024 * 1024;
// 一天的毫秒数 // 一天的毫秒数
@@ -24,27 +37,46 @@ const LifeOfFile = millisecondsOfOneDay * 3;
// 数据库相关 // 数据库相关
const db = new Database(dbPath, { verbose: null }); const db = new Database(dbPath, { verbose: null });
db.prepare( db.exec(
`CREATE TABLE IF NOT EXISTS id2fileName ( `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,
fileSize INT NOT NULL,
sender_id CHAR(32) NOT NULL
);` );`
).run(); );
db.exec(`
CREATE TABLE IF NOT EXISTS USERS(
user_id CHAR(32) PRIMARY KEY UNIQUE,
password_hash CHAR(32) NOT NULL,
user_group VARCHAR(32) NOT NULL,
uploadFilesSizeCount BIGINT NOT NULL,
email CHAR(64)
);`);
const stmt_getPermissionGroup = db.prepare("SELECT user_group FROM USERS WHERE user_id = ? AND password_hash = ?");
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`); const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`);
const stmt_getFileInfo = db.prepare(`SELECT fileName,fileSize FROM id2fileName WHERE id = ?`);
const stmt_getUserUploadFilesSizeCount = db.prepare("SELECT uploadFilesSizeCount FROM USERS WHERE user_id = ?");
const stmt_getUserGroup = db.prepare("SELECT user_group FROM USERS WHERE user_id = ?");
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`); const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`);
const stmt_getFileSize = db.prepare(`SELECT fileSize FROM id2fileName WHERE id = ?`);
const stmt_getFreeze = db.prepare(`SELECT freeze 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_getDownloadCredit = db.prepare(`SELECT downloadCredit FROM id2fileName WHERE id = ?`);
const stmt_getFreezenIDs = db.prepare(`SELECT id FROM id2fileName WHERE freeze = 1`); const stmt_getFreezenIDs = db.prepare(`SELECT id,fileSize,sender_id FROM id2fileName WHERE freeze = 1`);
const stmt_insertId2FileName = db.prepare(`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit) VALUES (?, ?, ?, ?, ?)`); const stmt_insertId2FileName = db.prepare(
`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit, fileSize, sender_id) VALUES (?, ?, ?, ?, ?, ?, ?)`
);
const stmt_deleteExpireData = db.prepare(`DELETE FROM id2fileName WHERE freeze=1`); 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_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 stmt_updateDownloadCredit = db.prepare(`UPDATE id2fileName SET downloadCredit=downloadCredit-1 WHERE id = ?`);
const stmt_updateUserUploadFilesSizeCount = db.prepare(`UPDATE USERS SET uploadFilesSizeCount=uploadFilesSizeCount+? WHERE user_id = ?`);
const stmt_updateUserUploadFilesSizeCount_d = db.prepare(`UPDATE USERS SET uploadFilesSizeCount=uploadFilesSizeCount-? WHERE user_id = ?`);
const db_insert = (id, fileName, downloadCredit) => { const db_insert = (id, fileName, downloadCredit, fileSize, senderId) => {
stmt_insertId2FileName.run(id, fileName, parseInt(Date.now()), 0, downloadCredit); stmt_insertId2FileName.run(id, fileName, parseInt(Date.now()), 0, downloadCredit, fileSize, senderId);
}; };
const db_getFileName = id => { const db_getFileName = id => {
try { try {
@@ -54,6 +86,30 @@ const db_getFileName = id => {
return null; return null;
} }
}; };
// const db_getFileInfo = id => {
// try {
// if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
// return stmt_getFileSize.get(id);
// } catch (e) {
// return null;
// }
// };
const db_getFileInfo = (function () {
const infoCache = {};
return function (id) {
if (infoCache[id]) {
return infoCache[id];
}
try {
if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
const info = stmt_getFileInfo.get(id);
infoCache[id] = info;
return info;
} catch (e) {
return null;
}
};
})();
// 检查是否可下载 // 检查是否可下载
const db_downloadable = id => { const db_downloadable = id => {
try { try {
@@ -78,6 +134,22 @@ const db_getTimestamp = id => {
return stmt_getTimestamp.get(id)["timestamp"]; return stmt_getTimestamp.get(id)["timestamp"];
}; };
const db_getFileSize = id => {
return stmt_getFileSize.get(id)["fileSize"];
};
const db_getUserUploadFilesSizeCount = id => {
return stmt_getUserUploadFilesSizeCount.get(id)["uploadFilesSizeCount"];
};
function db_updateUserUploadFilesSizeCount(id, size) {
return stmt_updateUserUploadFilesSizeCount.run(size, id);
}
// 减
function db_updateUserUploadFilesSizeCount_d(id, size) {
return stmt_updateUserUploadFilesSizeCount_d.run(size, id);
}
// 更新存储的文件,删除已经被冻结的文件以及相关记录,然后冻结过期文件 // 更新存储的文件,删除已经被冻结的文件以及相关记录,然后冻结过期文件
const updateDBAndFiles = () => { const updateDBAndFiles = () => {
try { try {
@@ -85,6 +157,9 @@ const updateDBAndFiles = () => {
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 sid = row["sender_id"];
let fsize = row["fileSize"];
db_updateUserUploadFilesSizeCount_d(sid, fsize);
let path = fileDir + "/" + fileName; let path = fileDir + "/" + fileName;
try { try {
fs.unlink(path, e => { fs.unlink(path, e => {
@@ -117,6 +192,15 @@ if (!fs.existsSync(fileDir)) {
fs.mkdirSync(fileDir); fs.mkdirSync(fileDir);
} }
function db_getPermissionGroup(id, pwdH) {
let res = stmt_getPermissionGroup.get(id, pwdH);
// console.log(id,pwdH,res)
if (res && res.user_group) {
return res.user_group;
}
return null;
}
function generateId(isGID) { function generateId(isGID) {
var returnStr = "", var returnStr = "",
range = 8, range = 8,
@@ -134,10 +218,11 @@ function generateId(isGID) {
} }
// 上传文件接口 // 上传文件接口
function uploadFile(req, res, fileName, downloadCredit) { function uploadFile(req, res, fileName, downloadCredit, fileSize) {
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, fileSize, req.sender_id);
db_updateUserUploadFilesSizeCount(req.sender_id, fileSize);
// tempMap[id] = fileName; // tempMap[id] = fileName;
// console.log(tempMap); // console.log(tempMap);
req.pipe(fileStream); req.pipe(fileStream);
@@ -184,9 +269,99 @@ function downloadFile(req, res, fileId) {
} }
} }
function getFileInfo(req, res, fileId) {
try {
if (db_downloadable(fileId) == false) {
res.writeHead(404);
res.end(null);
return;
}
// db_updateDownloadCredit(fileId);
let info = db_getFileInfo(fileId);
if (info == null) {
res.writeHead(404);
res.end(null);
return;
}
res.end(JSON.stringify(info));
} catch (err) {
console.log(err);
res.writeHead(404);
res.end(null);
}
}
const getUserGroup = (function () {
const cache = {};
return function (userId) {
if (!cache[userId]) {
cache[userId] = stmt_getUserGroup.get(userId)["user_group"];
}
return cache[userId];
};
})();
// 验证token
const verifyToken = (req, res, next) => {
try {
const token = req.headers.authorization.split(" ")[1]; // 从Authorization头中提取JWT
console.log(req.headers.authorization, token);
let verifyData = jwt.verify(token, JWT_SK);
if (verifyData.exp > parseInt(Date.now())) {
throw "token has expired";
}
req.sender_id = verifyData.id;
next();
} catch (err) {
console.log(err);
return res.status(401).json({ message: "Invalid token", err });
}
};
app.use(express.static(PATH)); app.use(express.static(PATH));
app.post("/user/login", express.json(), function (req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Methods", "POST");
// console.log(req.body)
let { id, pwd } = req.body;
console.log(req.body);
let pwdH = sha256(id + pwd).toString();
let userGroup = db_getPermissionGroup(id, pwdH);
if (userGroup !== null) {
res.writeHead(200);
let token = jwt.sign({ id, pwd }, JWT_SK, { expiresIn: `${Config.tokenExpiresIn}h` });
res.end(JSON.stringify({ status: "Success", userGroup, token }));
} else {
res.end(JSON.stringify({ status: "Error" }));
}
// console.log(req)
});
app.post("/user/refrashToken", express.json(), function (req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "*");
res.header("Access-Control-Allow-Methods", "POST");
// console.log(req.body)
try {
const token = req.headers.authorization.split(" ")[1]; // 从Authorization头中提取JWT
// const token = req.body.token
console.log(token)
let verifyData = jwt.verify(token, JWT_SK);
if (verifyData.exp > parseInt(Date.now())) {
return res.status(401).json({ status: "Error", message: "Invalid token" });
}
let id = verifyData.id;
let pwd = verifyData.pwd;
let new_token = jwt.sign({ id, pwd }, JWT_SK, { expiresIn: `${Config.tokenExpiresIn}h` });
console.log({ token, new_token });
res.end(JSON.stringify({ status: "Success", new_token }));
} catch (e) {
return res.status(401).json({ status: "Error", message: "Invalid token" });
}
});
// app.all() // app.all()
app.post("/file/upload", function (req, res) { app.post("/file/upload", verifyToken, 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");
@@ -198,15 +373,18 @@ app.post("/file/upload", function (req, res) {
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 ||
db_getUserUploadFilesSizeCount(req.sender_id) + fileSize > Config.userGroupSetting[getUserGroup(req.sender_id)].cloudCapacity
) {
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, fileSize);
} }
}); });
app.get("/file/download", function (req, res) { app.get("/file/download", verifyToken, 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");
@@ -220,6 +398,20 @@ app.get("/file/download", function (req, res) {
} }
}); });
app.get("/file/info", verifyToken, 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 {
getFileInfo(req, res, fileId);
}
});
// // 启动服务器 // // 启动服务器
// app.listen(PORT, HOST, function(){ // app.listen(PORT, HOST, function(){
// console.log(app) // console.log(app)
@@ -230,33 +422,33 @@ const server = http.createServer(app);
const wss = new WebSocketServer({ server }); const wss = new WebSocketServer({ server });
// 先实现最低限度功能,暂时不考虑身份认证 // 先实现最低限度功能,暂时不考虑身份认证
const groupsMap = {}; const groupsMap = {};
// 三天不活跃的群组将被清除 // 一天不活跃的群组将被清除
const LifeOfGroup = millisecondsOfOneDay * 3; const LifeOfGroup = millisecondsOfOneDay * 1;
// const LifeOfGroup = 0; // const LifeOfGroup = 0;
// 根据uuid查询加入的群组 // 根据uuid查询加入的群组
const uuid2Groups={} const uuid2Groups = {};
db.exec( db.exec(
`CREATE TABLE IF NOT EXISTS GROUPS ( `CREATE TABLE IF NOT EXISTS CHATGROUPS (
gID CHAR(8) PRIMARY KEY UNIQUE NOT NULL, gID CHAR(8) PRIMARY KEY UNIQUE NOT NULL,
verify TEXT NOT NULL, verify TEXT NOT NULL,
activeTimestamp INT NOT NULL activeTimestamp INT NOT NULL
);` );`
); );
const stmt_getExpiredGroups = db.prepare(`SELECT gID FROM GROUPS WHERE activeTimestamp < ? `); const stmt_getExpiredGroups = db.prepare(`SELECT gID FROM CHATGROUPS WHERE activeTimestamp < ? `);
const stmt_getGroupVerify = db.prepare(`SELECT verify FROM GROUPS WHERE gID = ? `); const stmt_getGroupVerify = db.prepare(`SELECT verify FROM CHATGROUPS WHERE gID = ? `);
const stmt_insertGROUPS = db.prepare(`INSERT INTO GROUPS (gID,verify, activeTimestamp) VALUES (?, ?, ?)`); const stmt_insertCHATGROUPS = db.prepare(`INSERT INTO CHATGROUPS (gID,verify, activeTimestamp) VALUES (?, ?, ?)`);
const stmt_updateGROUPS_ActiveTimestamp = db.prepare(`UPDATE GROUPS SET activeTimestamp=? WHERE gID=?`); const stmt_updateCHATGROUPS_ActiveTimestamp = db.prepare(`UPDATE CHATGROUPS SET activeTimestamp=? WHERE gID=?`);
const stmt_deleteGroup = db.prepare(`DELETE FROM GROUPS WHERE gID=?`); const stmt_deleteGroup = db.prepare(`DELETE FROM CHATGROUPS WHERE gID=?`);
const generateStmt_insertMessage_map = {}; const generateStmt_insertMessage_map = {};
const updateGROUPS = () => { const updateCHATGROUPS = () => {
// console.log(1) // console.log(1)
try { try {
let gIDs = stmt_getExpiredGroups.all(parseInt(Date.now()) - LifeOfGroup); let gIDs = stmt_getExpiredGroups.all(parseInt(Date.now()) - LifeOfGroup);
@@ -270,8 +462,8 @@ const updateGROUPS = () => {
return; return;
} }
}; };
updateGROUPS(); updateCHATGROUPS();
setInterval(updateGROUPS, 1000 * 3600 * 3); setInterval(updateCHATGROUPS, 1000 * 3600 * 3);
// 创建群组的数据库 // 创建群组的数据库
function createGroupTableDB(gID, verify) { function createGroupTableDB(gID, verify) {
@@ -279,23 +471,24 @@ function createGroupTableDB(gID, verify) {
`CREATE TABLE IF NOT EXISTS ${gID} ( `CREATE TABLE IF NOT EXISTS ${gID} (
message TEXT NOT NULL, message TEXT NOT NULL,
type TEXT NOT NULL, type TEXT NOT NULL,
type1 TEXT NOT NULL,
sender TEXT NOT NULL, sender TEXT NOT NULL,
timestamp INT NOT NULL timestamp INT NOT NULL
);` );`
); );
stmt_insertGROUPS.run(gID, verify, parseInt(Date.now())); stmt_insertCHATGROUPS.run(gID, verify, parseInt(Date.now()));
} }
// 插入信息 // 插入信息
function insertMessageByGID(gID, msg, from) { function insertMessageByGID(gID, msg, type1, from, timestamp) {
if (!generateStmt_insertMessage_map[gID]) { if (!generateStmt_insertMessage_map[gID]) {
generateStmt_insertMessage_map[gID] = db.prepare(`INSERT INTO ${gID} (message,type,sender, timestamp) VALUES (?, ?, ?, ?)`); generateStmt_insertMessage_map[gID] = db.prepare(`INSERT INTO ${gID} (message,type,type1,sender, timestamp) VALUES (?, ?, ?, ?, ?)`);
} }
generateStmt_insertMessage_map[gID].run(msg, "gMsg", from, parseInt(Date.now())); generateStmt_insertMessage_map[gID].run(msg, "gMsg", type1, from, timestamp);
} }
function updateActiveTimestamp(gID) { function updateActiveTimestamp(gID) {
stmt_updateGROUPS_ActiveTimestamp.run(parseInt(Date.now()), gID); stmt_updateCHATGROUPS_ActiveTimestamp.run(parseInt(Date.now()), gID);
} }
function createGroup(verify) { function createGroup(verify) {
@@ -307,13 +500,13 @@ function createGroup(verify) {
} }
function getGroupVerify(gID) { function getGroupVerify(gID) {
initMsgHistory(gID) initMsgHistory(gID);
return groupsMap[gID].verify; return groupsMap[gID].verify;
} }
// 正式加入群组 // 正式加入群组
function joinGroup(socket, gID, callback) { function joinGroup(socket, gID, callback) {
initMsgHistory(gID) initMsgHistory(gID);
groupsMap[gID].member.push(socket); groupsMap[gID].member.push(socket);
updateActiveTimestamp(gID); updateActiveTimestamp(gID);
callback(); callback();
@@ -321,31 +514,32 @@ function joinGroup(socket, gID, callback) {
// 暂时这么写 // 暂时这么写
function getHistory(gID) { function getHistory(gID) {
initMsgHistory(gID) initMsgHistory(gID);
return groupsMap[gID].msgHistory; return groupsMap[gID].msgHistory;
} }
function broadcastMsg(gID, msg, from) { function broadcastMsg(gID, msg, type1, from) {
initMsgHistory(gID) const timestamp = parseInt(Date.now());
initMsgHistory(gID);
updateActiveTimestamp(gID); updateActiveTimestamp(gID);
insertMessageByGID(gID, msg, from); insertMessageByGID(gID, msg, type1, from, timestamp);
let bmsg = JSON.stringify({ type: "gMsg", gID, msg }); let bmsg = JSON.stringify({ type: `gMsg`, type1, gID, msg, from, timestamp });
groupsMap[gID].msgHistory.push({ type: "gMsg", msg, from }); groupsMap[gID].msgHistory.push({ type: `gMsg`, type1, msg, from, timestamp });
for (let socket of groupsMap[gID].member) { for (let socket of groupsMap[gID].member) {
socket.send(bmsg); socket.send(bmsg);
} }
} }
function initMsgHistory(gID){ function initMsgHistory(gID) {
if (!groupsMap[gID]) { if (!groupsMap[gID]) {
let verify = stmt_getGroupVerify.get(gID).verify; let verify = stmt_getGroupVerify.get(gID).verify;
// 暂时这么写 // 暂时这么写
let msgHistory = db let msgHistory = db
.prepare(`SELECT message,type FROM ${gID}`) .prepare(`SELECT message,type,type1,sender,timestamp FROM ${gID}`)
.all() .all()
.map(v => { .map(v => {
return { msg: v.message, type: v.type }; return { type: v.type, type1: v.type1, msg: v.message, from: v.sender, timestamp: v.timestamp };
}); });
// console.log(msgHistory) // console.log(msgHistory)
groupsMap[gID] = { verify, member: [], msgHistory }; groupsMap[gID] = { verify, member: [], msgHistory };
@@ -358,8 +552,8 @@ wss.on("connection", socket => {
const no = wss.clients.size; const no = wss.clients.size;
const joinedGroup = []; const joinedGroup = [];
var uuid = uuid4(); var uuid = uuid4();
function setUuid(new_uuid){ function setUuid(new_uuid) {
uuid = new_uuid uuid = new_uuid;
} }
socket.on("message", message => { socket.on("message", message => {
try { try {
@@ -391,14 +585,14 @@ wss.on("connection", socket => {
} }
} else if (type === "message") { } else if (type === "message") {
let gID = msg.gID; let gID = msg.gID;
broadcastMsg(gID, msg.msg,uuid); broadcastMsg(gID, msg.msg, msg.type1, uuid);
} else if (type === "heartbeat") { } else if (type === "heartbeat") {
socket.send(JSON.stringify({ type: "sMsg", type1: "heartbeat" })); socket.send(JSON.stringify({ type: "sMsg", type1: "heartbeat" }));
} else if (type === "uuid") { } else if (type === "uuid") {
if (msg.type1 === "get") { if (msg.type1 === "get") {
socket.send(JSON.stringify({ type: "sMsg", type1: "uuid", uuid })); socket.send(JSON.stringify({ type: "sMsg", type1: "uuid", uuid }));
} else if (msg.type1 === "set") { } else if (msg.type1 === "set") {
uuid = msg.uuid uuid = msg.uuid;
} }
} }
} }
+117 -44
View File
@@ -3,87 +3,159 @@ import upload from "./views/uploadFileMiao.vue";
import download from "./views/downloadFileMiao.vue"; import download from "./views/downloadFileMiao.vue";
import setting from "./views/setting.vue"; import setting from "./views/setting.vue";
import chat from "./views/chat.vue"; import chat from "./views/chat.vue";
import { NNotificationProvider, NMessageProvider, NCard, NMenu, NIcon } from 'naive-ui' import login from "./views/login.vue";
import { ref, watch, h ,shallowRef} from "vue"; import account from "./views/accountInfo.vue";
// import { RouterView, useRouter } from "vue-router"; import getQueryString from '@/func/getQuery'
import { NNotificationProvider, NMessageProvider, NCard, NMenu, NIcon, NModalProvider } from 'naive-ui'
import { ref, watch, h, shallowRef, onMounted, computed } from "vue";
// import { useRoute } from "vue-router";
import { import {
ArrowUpCircleOutline as uploadIcon, CloudUploadOutline as uploadIcon,
ArrowDownCircleOutline as downloadIcon, CloudDownloadOutline as downloadIcon,
ChatboxEllipsesOutline as chatIcon, // ChatboxEllipsesOutline as chatIcon,
SettingsOutline as settingIcon ChatbubblesOutline as chatIcon,
SettingsOutline as settingIcon,
FootstepsOutline as AccountIcon
} from '@vicons/ionicons5' } from '@vicons/ionicons5'
import { useSettingStore } from "@/pinia/store";
import config from "../miaoShareConfig.json"
// console.log(process.env.NODE_ENV)
const settingData = useSettingStore();
const serverPort = settingData.serverPort;
const baseUrl =
location.protocol + "//" + location.hostname + ":" + serverPort + "/";
const tokenExpiresIn = config.tokenExpiresIn
const activePage = ref('upload') const activePage = ref('upload')
const isComponent = shallowRef(upload) const isComponent = shallowRef(upload)
watch(activePage, () => { watch(activePage, () => {
isComponent.value={upload,download,setting,chat}[activePage.value] isComponent.value = { upload, download, setting, chat, login, account }[activePage.value]
}) })
function renderIcon(icon) { function renderIcon(icon) {
return () => h(NIcon, null, { default: () => h(icon) }) return () => h(NIcon, null, { default: () => h(icon) })
} }
const menuOptions = [ const menuOptions = ref([
{ {
label: '上传', label: '上传',
key: 'upload', key: 'upload',
icon: renderIcon(uploadIcon), icon: renderIcon(uploadIcon),
disabled: computed(() => {
// console.log(settingData)
return settingData.token === null
})
}, },
{ {
label: '下载', label: '下载',
key: 'download', key: 'download',
icon: renderIcon(downloadIcon), icon: renderIcon(downloadIcon),
disabled: computed(() => {
return settingData.token === null
})
}, },
{ {
label: 'Chat', label: 'Chat',
key: 'chat', key: 'chat',
icon: renderIcon(chatIcon), icon: renderIcon(chatIcon),
disabled: computed(() => {
return settingData.token === null
})
}, },
{ {
label: '设置', label: '设置',
key: 'setting', key: 'setting',
icon: renderIcon(settingIcon), icon: renderIcon(settingIcon),
disabled: computed(() => {
return settingData.token === null
})
},
{
label: '账户',
key: 'account',
icon: renderIcon(AccountIcon),
disabled: computed(() => {
return settingData.token === null
})
} }
] ])
// function getQueryString(key) {
// var reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)', 'i');
// var r = window.location.search.substr(1).match(reg);
// if (r != null) {
// return unescape(r[2]);
// }
// return null;
// }
onMounted(async () => {
let aP = getQueryString('aP')
// console.log(aP)
if (aP) {
activePage.value = aP
}
if (settingData.getToken() === null) {
aP = activePage.value
activePage.value = 'login'
// 登录后跳原来的activePage
const w = watch(() => settingData.token, () => {
activePage.value = aP
w()
})
} else {
// 刷新token
(async function refreshToken() {
const old_token = settingData.getToken()
console.log(old_token)
const response = await fetch(`${baseUrl}user/refrashToken`, {
// credentials: 'include',
method: "POST",
headers: {
'Authorization': `Bearer ${settingData.token}`,
"Content-Type": "application/json"
},
// body: JSON.stringify({
// token:settingData.token
// })
});
console.log(response)
let data = await response.json()
if (data.status === 'Error') {
console.log('falied')
} else {
localStorage.setItem('token', data.new_token)
localStorage.setItem('tokenExpiredTime', parseInt(Date.now()) + parseInt(tokenExpiresIn) * 3600000)
}
})()
}
})
</script> </script>
<template> <template>
<div id="windows"> <div id="windows">
<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-modal-provider>
<div id="menu"> <n-card content-style="padding: 0; margin:0" id="theCard">
<n-menu mode="horizontal" :responsive="true" v-model:value="activePage" :options="menuOptions" :icon-size="26"></n-menu> <div id="menu">
</div> <n-menu mode="horizontal" :responsive="true" v-model:value="activePage"
:options="menuOptions" :icon-size="26"></n-menu>
</div>
<div style="padding: 20px;"> <div style="padding: 20px;">
<KeepAlive> <KeepAlive>
<component :is="isComponent"></component> <component :is="isComponent"></component>
</KeepAlive> </KeepAlive>
<!-- <router-view v-slot="{ Component }"> </div>
<keep-alive :include=['uploadFileMiao']> </n-card>
<component :is="Component" :key="$route.fullPath"/> </n-modal-provider>
</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-card>
</n-message-provider> </n-message-provider>
</n-notification-provider> </n-notification-provider>
</div> </div>
@@ -105,14 +177,15 @@ const menuOptions = [
height: 90vh; height: 90vh;
} }
#menu{ #menu {
width: 100%; width: 100%;
height: auto; height: auto;
/* background-color: blue; */ /* background-color: blue; */
padding-top: 5px; padding-top: 5px;
border-bottom: 1px solid rgb(60, 60, 60, 0.1); border-bottom: 1px solid rgb(60, 60, 60, 0.1);
user-select: none user-select: none
} }
@media screen and (max-width:768px) { @media screen and (max-width:768px) {
#theCard { #theCard {
width: 100vw; width: 100vw;
+262
View File
@@ -0,0 +1,262 @@
<template>
<!-- <div style="border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;"> -->
<n-scrollbar
style="padding: 10px;padding-right: 15px; border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;">
<n-image-group>
<div v-for="msg of props.messages">
<div style="color:slategrey;">{{ `${msg.from.split('-')[0]} ${msg.timestampString}` }}</div>
<div style="background-color: rgb(239, 239, 245); padding: 2px 7px; width: fit-content; border-radius: 5px; white-space: pre-wrap;"
v-if="msg.type1 === 'text'">
<span>{{ msg.msg }}</span>
<!-- <span v-if="msg.type1 === 'img'"> -->
<!-- <n-image :src="imgUrlCache[msg.msg]" width="100" style="margin-top: 5px;border-radius: 5px;" /> -->
<!-- img->>{{ msg.msg }} -->
<!-- </span> -->
</div>
<div v-if="msg.type1 === 'img'">
<span>
<n-image :src="imgUrlCache[msg.msg]" width="100" style="margin-top: 5px;border-radius: 5px;" />
<n-skeleton v-show="!imgUrlCache[msg.msg]" height="80px" width="100px" :sharp="false"
style="border-radius: 5px;" />
</span>
</div>
<div v-if="msg.type1 === 'file'"
style="background-color: rgb(239, 239, 245); padding: 2px 7px; width: fit-content; border-radius: 5px;">
<n-flex style="justify-content: center;align-items: center;user-select: none;cursor: pointer;"
@click="downloadFile(msg.msg)">
<n-icon :component="DownloadIcon" size="50" style="margin-top: 0px;"></n-icon>
<n-flex vertical v-if="fileInfoCache[msg.msg]">
<div style="font-weight: 500;margin-top: 8px;font-size: large;">
{{ fileInfoCache[msg.msg].fileName ? fileInfoCache[msg.msg].fileName : 'Unknow' }}
</div>
<n-flex style="margin-bottom: 8px;" justify="space-between">
<div>
{{ fileInfoCache[msg.msg].fileSize ? fileInfoCache[msg.msg].fileSize : 'Unknow'
}}
</div>
<!-- lets show u a bullshit -->
<div>
{{ fileStatus2String[fileInfoCache[msg.msg].status ? fileInfoCache[msg.msg].status :
'Unknow'] ? fileStatus2String[fileInfoCache[msg.msg].status ?
fileInfoCache[msg.msg].status : 'Unknow'] : fileInfoCache[msg.msg].status ?
fileInfoCache[msg.msg].status : 'Unknow' }}
</div>
</n-flex>
</n-flex>
</n-flex>
<!-- {{ fileInfoCache[msg.msg] }} -->
</div>
</div>
<n-empty description="无可显示消息" v-show="props.messages.length === 0" style="margin-top: 25vh;">
<template #extra>
</template>
</n-empty>
<div style="height: 30px;" v-show="props.messages.length !== 0"></div>
</n-image-group>
</n-scrollbar>
<!-- </div> -->
</template>
<script setup>
import { NScrollbar, NEmpty, NImage, NImageGroup, NSkeleton, NIcon, NFlex } from 'naive-ui'
import { ref, watch } from 'vue';
import { useSettingStore } from "@/pinia/store";
import { decrypt, decryptString,downloadFromUint8Array } from '../func/download'
import { CubeOutline as DownloadIcon } from '@vicons/ionicons5'
import fileSize2String from '@/func/fileSize2String'
const setting = useSettingStore();
const serverPort = setting.serverPort;
const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
const props = defineProps({
messages: {
type: Array,
required: true
},
})
async function getFileInfo(code) {
let id = code.substring(0, 8);
const params = {
fileName: id
};
const options = {
method: "GET",
headers: {
'Authorization': `Bearer ${setting.token}`
// 'Content-Type':'application/json'
},
};
let response = await fetch(baseUrl + "file/info?" + new URLSearchParams(params), options)
let info = await response.json()
return info
}
async function downloadFile(code) {
if (fileInfoCache.value[code].status === 'waittingDownload') {
fileInfoCache.value[code].status = `downloading`;
return new Promise((resolve, reject) => {
let id = code.substring(0, 8);
let key = code.substring(8);
const params = {
fileName: id
};
const options = {
method: "GET",
headers: {
'Authorization': `Bearer ${setting.token}`
},
};
fetch(baseUrl + "file/download?" + new URLSearchParams(params), options)
.then(res => {
// fileName = res.headers.get("File-Name");
// fileName = decryptString(key, fileName);
// updateDownloadQuene(code, "fileName", fileName);
const total = +res.headers.get("Content-Length");
let loaded = 0;
const clone = res.clone();
const reader = res.body.getReader();
// 感觉怪怪的
const read = async () => {
const { done, value } = await reader.read();
if (done) {
// console.log(clone)
return clone;
}
loaded += value.length;
const percent = (loaded / total) * 100;
fileInfoCache.value[code].status = `下载进度:${percent.toFixed(2)}%`;
return read();
};
return read();
})
.then(res => {
var data = res.blob();
return data;
})
.then(data => {
decrypt(key, data).then(data => {
fileInfoCache.value[code].status = `finish`;
fileInfoCache.value[code].data = data
downloadFromUint8Array(data,fileInfoCache.value[code].fileName)
resolve(data)
});
})
.catch(err => {
fileInfoCache.value[code].status = `error`;
console.error(err);
});
})
}else if(fileInfoCache.value[code].status === 'finish'){
downloadFromUint8Array(fileInfoCache.value[code].data,fileInfoCache.value[code].fileName)
}
};
async function downloadImage(code) {
return new Promise((resolve, reject) => {
var id = code.substring(0, 8);
var key = code.substring(8);
var fileName;
const params = {
fileName: id
};
const options = {
method: "GET",
headers: {
'Authorization': `Bearer ${setting.token}`
// 'Content-Type':'application/json'
},
};
fetch(baseUrl + "file/download?" + new URLSearchParams(params), options)
.then(res => {
// fileName = res.headers.get("File-Name");
// fileName = decryptString(key, fileName);
// updateDownloadQuene(code, "fileName", fileName);
const total = +res.headers.get("Content-Length");
let loaded = 0;
const clone = res.clone();
const reader = res.body.getReader();
// 感觉怪怪的
const read = async () => {
const { done, value } = await reader.read();
if (done) {
// console.log(clone)
return clone;
}
loaded += value.length;
// const percent = (loaded / total) * 100;
// downloadProcess['downloadProcess'] = `下载进度:${percent.toFixed(2)}%`;
// updateDownloadQuene(
// code,
// "downloadProcess",
// `下载进度:${percent.toFixed(2)}%`
// );
// downloadProcess.value = percent.toFixed(2);
return read();
};
return read();
})
.then(res => {
var data = res.blob();
return data;
})
.then(data => {
// downloadProcess['downloadProcess'] = '解密中'
// updateDownloadQuene(code, "downloadProcess", `解密中`);
decrypt(key, data).then(data => {
// updateDownloadQuene(code, "downloadProcess", `已完成`);
// const url = URL.createObjectURL(new Blob(data))
resolve(data)
});
})
.catch(err => {
// updateDownloadQuene(
// code,
// "fileName",
// `出错,可能是文件过期或下载次数用完`
// );
// updateDownloadQuene(code, "downloadProcess", `出错`);
console.error(err);
});
})
};
const imgUrlCache = ref({})
const fileInfoCache = ref({})
const fileStatus2String = ref({
waittingDownload: '点击下载',
downloading: '下载中',
finish: '下载完成',
Unknow: '获取文件信息中',
error: '出错'
})
const imgDownloadingQueue = []
const getFileInfoQueue = []
watch(() => props.messages, async () => {
for (let msg of props.messages) {
if (msg.type1 === 'img') {
if (!imgDownloadingQueue.includes(msg.msg)) {
imgDownloadingQueue.push(msg.msg)
const imgData = await downloadImage(msg.msg)
const url = URL.createObjectURL(new Blob([imgData], { type: "image/png" }))
imgUrlCache.value[msg.msg] = url
}
} else if (msg.type1 === 'file') {
if (!getFileInfoQueue.includes(msg.msg)) {
getFileInfoQueue.push(msg.msg)
let key = msg.msg.substring(8);
const info = await getFileInfo(msg.msg)
info.fileName = decryptString(key, info.fileName)
info.fileSize = fileSize2String(info.fileSize)
info.status = 'waittingDownload'
fileInfoCache.value[msg.msg] = info
console.log(fileInfoCache.value)
}
}
}
})
</script>
<style></style>
+42
View File
@@ -0,0 +1,42 @@
import workerpool from 'workerpool'
function randName(){
return `randName${Math.ceil(Math.random()*100000)}`
}
class MiaoWorkerPool{
constructor(){
this.workers = {}
this.pool = workerpool.pool()
}
/**
* Description 添加function
* @param {function} func 待添加function
* @returns {String} 返回添加的function名
*/
init(func){
let funcName = func.name.length > 0 ? func.name : randName();
this[funcName] = async (...args)=>{
try{
let result = await this.pool.exec(func,args)
return {
isSuccess:true,
result
}
}catch(e){
throw {
isSuccess:false,
result:e
}
}
}
return funcName
}
}
export default MiaoWorkerPool
+23 -8
View File
@@ -41,7 +41,7 @@ const decrypt = (key, data) => {
}; };
const decryptString = (key, str) => { const decryptString = (key, str) => {
key = key.trim() key = key.trim()
console.log(key.length) // console.log(key.length)
// 无加密 // 无加密
if (key.length === 0) { if (key.length === 0) {
// return sm4.decrypt(str, "miaospringmiaospringmiaospringao"); // return sm4.decrypt(str, "miaospringmiaospringmiaospringao");
@@ -54,23 +54,38 @@ const decryptString = (key, str) => {
return sm4.decrypt(str, key); return sm4.decrypt(str, key);
} }
// AES // AES
var data = new Uint8Array( // var data = new Uint8Array(
Base64.decode(str) // Base64.decode(str)
.split(",") // .split(",")
.map(n => parseInt(n)) // .map(n => parseInt(n))
); // );
var data = Base64.toUint8Array(str)
// console.log(data)
// data = new TextDecoder().decode(data)
key = aesjs.utils.utf8.toBytes(key); key = aesjs.utils.utf8.toBytes(key);
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
var decryptedBytes = aesCtr.decrypt(data); var decryptedBytes = aesCtr.decrypt(data);
var decryptedStr = aesjs.utils.utf8.fromBytes(decryptedBytes); // var decryptedStr = aesjs.utils.utf8.fromBytes(decryptedBytes);
var decryptedStr = new TextDecoder().decode(decryptedBytes)
return decryptedStr; return decryptedStr;
}; };
const _saveAs = (blob,fileName)=>{
const blobUrl = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.download = fileName;
a.href = blobUrl;
a.click();
}
const downloadFromUint8Array = (data, fileName) => { const downloadFromUint8Array = (data, fileName) => {
let uint8Array = data; let uint8Array = data;
let blob = new Blob([uint8Array], { type: "text/plain" }); let blob = new Blob([uint8Array], { type: "application/octet-stream" });
// const blobUrl = window.URL.createObjectURL(blob);
// 使用file-saver.js // 使用file-saver.js
saveAs(blob, fileName); saveAs(blob, fileName);
// _saveAs(blob, fileName);
}; };
export { blob2Uint8Array, decrypt, decryptString, downloadFromUint8Array }; export { blob2Uint8Array, decrypt, decryptString, downloadFromUint8Array };
+14
View File
@@ -0,0 +1,14 @@
export default function filePicker({ fn, accept, multiple } = {}) {
const inpEle = document.createElement("input");
inpEle.id = `__file_${parseInt(Math.random() * 100000000)}`;
inpEle.type = "file";
inpEle.style.display = "none";
// 文件类型限制
// inpEle.accept="image/*"
accept && (inpEle.accept = accept);
// 多选限制
multiple && (inpEle.multiple = multiple);
// inpEle.addEventListener("change", event => fn.call(inpEle, event, inpEle.files), { once: true });
inpEle.addEventListener("change", e => fn(inpEle.files), { once: true });
inpEle.click();
}
+12
View File
@@ -0,0 +1,12 @@
function pow1024(num) {
return Math.pow(1024, num)
}
export default function(size){
if (!size) return '';
return size < 1024 ? size + ' B' :
size < pow1024(2) ? (size / 1024).toFixed(2) + ' KB' :
size < pow1024(3) ? (size / pow1024(2)).toFixed(2) + ' MB' :
size < pow1024(4) ? (size / pow1024(3)).toFixed(2) + ' GB' :
(size / pow1024(4)).toFixed(2) + ' TB'
}
+8
View File
@@ -0,0 +1,8 @@
export default function getQueryString(key) {
var reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substring(1).match(reg);
if (r != null) {
return decodeURI(r[2]);
}
return null;
}
+15 -6
View File
@@ -48,6 +48,8 @@ const blob2Uint8Array = function(blob) {
}; };
}); });
}; };
const AES = (key,data)=>{ const AES = (key,data)=>{
key = aesjs.utils.utf8.toBytes(key); key = aesjs.utils.utf8.toBytes(key);
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
@@ -71,15 +73,19 @@ const encrypt = (key, data, fileName, setting, message) => {
encrypted_data = data encrypted_data = data
// AES // AES
}else if(encryptFunction != "SM4"){ }else if(encryptFunction != "SM4"){
message.info(`开始加密${fileName}`); if(message)
message.info(`开始加密${fileName}`);
encrypted_data = AES(key,data) encrypted_data = AES(key,data)
message.info(`${fileName}加密完成`); if(message)
message.info(`${fileName}加密完成`);
// SM4 // SM4
}else{ }else{
message.info(`开始加密${fileName}`); if(message)
message.info(`开始加密${fileName}`);
key = [...aesjs.utils.utf8.toBytes(key.substring(3))]; key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
encrypted_data = SM4(key,data) encrypted_data = SM4(key,data)
message.info(`${fileName}加密完成`); if(message)
message.info(`${fileName}加密完成`);
} }
return encrypted_data return encrypted_data
}); });
@@ -97,10 +103,13 @@ const encryptString = (key, str, setting) => {
return Base64.encode(str) return Base64.encode(str)
}else if(encryptFunction !== "SM4"){ }else if(encryptFunction !== "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);
var data = new TextEncoder().encode(str)
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
var encryptedBytes = aesCtr.encrypt(data); var encryptedBytes = aesCtr.encrypt(data);
return Base64.encode(encryptedBytes); var res = Base64.fromUint8Array(encryptedBytes,true);
// console.log(encryptedBytes,res)
return res;
}else{ }else{
key = [...aesjs.utils.utf8.toBytes(key.substring(3))]; key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
// console.log(key) // console.log(key)
+43 -2
View File
@@ -1,6 +1,18 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import config from "../../miaoShareConfig.json" import config from "../../miaoShareConfig.json"
const userID = localStorage.getItem('userID');
const userGroup = localStorage.getItem('userGroup');
const tokenExpiresIn = config.tokenExpiresIn
if(localStorage.getItem('tokenExpiredTime') == null ||
parseInt(localStorage.getItem('tokenExpiredTime')) < parseInt(Date.now())){
localStorage.removeItem('token')
}
const token = localStorage.getItem('token');
// console.log(token)
var activeFuctionLocal = localStorage.getItem('activeFuction') var activeFuctionLocal = localStorage.getItem('activeFuction')
if (activeFuctionLocal == null){ if (activeFuctionLocal == null){
@@ -14,16 +26,45 @@ if (activeKeyMap == null ){
activeKeyMap = JSON.stringify(['A-Z','0-9']) activeKeyMap = JSON.stringify(['A-Z','0-9'])
localStorage.setItem('activeKeyMap',activeKeyMap) localStorage.setItem('activeKeyMap',activeKeyMap)
} }
// 开发环境下服务器端口使用设置的端口,否则使用与页面相同的端口
const serverPort = process.env.NODE_ENV === 'development' ? config.PORT : location.port
// console.log(process.env.NODE_ENV,config.DEVPORT,location.port,serverPort)
export const useSettingStore = defineStore("setting", { export const useSettingStore = defineStore("setting", {
state: () => ({ state: () => ({
activeFuction: activeFuctionLocal, activeFuction: activeFuctionLocal,
activeKeyMap:JSON.parse(activeKeyMap), activeKeyMap:JSON.parse(activeKeyMap),
serverPort:config.PORT enableGuest:config.enableGuest,
}), serverPort,
token,
userID,
userGroup
}),
getters: { getters: {
// double: state => state.count * 2 // double: state => state.count * 2
}, },
actions: { actions: {
setJWT( token,keepTokenLocal ){
this.token = token
if(keepTokenLocal){
localStorage.setItem('token',token)
localStorage.setItem('tokenExpiredTime',parseInt(Date.now())+parseInt(tokenExpiresIn)*3600000)
}
},
setUserID( id,keepTokenLocal ){
this.userID = id
if(keepTokenLocal)
localStorage.setItem('userID',id)
},
setUserGroup( g,keepTokenLocal ){
this.userGroup = g
if(keepTokenLocal)
localStorage.setItem('userGroup',g)
},
getToken(){
return localStorage.getItem('token')
}
// increment() { // increment() {
// this.count++; // this.count++;
// } // }
+18
View File
@@ -0,0 +1,18 @@
<template>
<n-button @click="signOut" type="error">
退出登录
</n-button>
</template>
<script setup>
import { NCheckbox, NInputGroupLabel, NInputGroup, NP, NFlex, NDataTable, NButton, NInput, NCard } from 'naive-ui'
function signOut(){
const items = ['token']
items.forEach(v=>{
localStorage.removeItem(v)
})
location.reload();
}
</script>
+290 -83
View File
@@ -1,52 +1,137 @@
<template> <template>
<n-button @click="createGroup"> <n-flex vertical>
createTest <n-flex style="padding-top: 10px;">
</n-button> <n-input v-model:value="groupCode" type="text" placeholder="输入邀请码" style="flex:1">
<n-text> createTest gID:{{ gID }}<br />key:{{ key }}<br />code:{{ gID+key }}</n-text> <template #suffix>
<n-input <n-icon :component="CopyOutline" style="cursor: pointer;" @click='toClipboard(groupCode)' />
v-model:value="codeInput" </template>
type="textarea" </n-input>
placeholder="测试加入群组" <n-flex>
/> <n-button @click="createGroup" :disabled="groupCode !== ''" :type="groupCode !== '' ? 'default' : 'primary'"
<n-button @click="joinGroup(codeInput)"> ghost>
joinTest 创建群组
</n-button> </n-button>
<n-input <n-button @click="joinGroup(groupCode)" :disabled="groupCode === ''"
v-model:value="msg" :type="groupCode === '' ? 'default' : 'primary'" ghost>
type="textarea" 加入群组
placeholder="测试发送信息" </n-button>
/> <n-button @click="shareWithQR()" :disabled="activeGID === null" v-show="activeGID !== null"
<n-button @click="sendMsg(codeInput.substring(0,8),msg)"> :type="activeGID === null ? 'default' : 'primary'">
sendMsgTest 分享群组
</n-button> </n-button>
<n-button @click="data.length = 0"> <n-select v-model:value="activeGID" :options="joinedGroup.map(gid => { return { label: gid, value: gid } })" v-show="activeGID !== null"
clearMsg placeholder="未加入群组" style="flex:1;user-select: none;" />
</n-button> </n-flex>
<n-data-table </n-flex>
:columns="columns" <!-- <n-flex style="padding-top: 10px;padding-bottom: 10px;">
:data="data" <n-select v-model:value="activeGID" :options="joinedGroup.map(gid => { return { label: gid, value: gid } })"
:row-props="null" placeholder="选择当前查看群组" style="flex:1" />
:max-height="250" <n-button @click="shareWithQR()" :disabled="activeGID === null"
:type="activeGID === null ? 'default' : 'primary'">
分享群组
</n-button>
</n-flex> -->
<!-- <n-data-table :columns="columns"
:data="activeGID ? Messages[activeGID].map(v => { v.timestampString = new Date(v.timestamp).toLocaleString(); return v }).reverse() : []"
:row-props="null" :bordered="false" :single-line="false" :max-height="400" /> -->
<div style="flex:1; position: relative;min-height: none;">
<chatui style="margin-bottom: 10px; height: 65vh"
:messages="activeGID ? Messages[activeGID].map(v => { v.timestampString = new Date(v.timestamp).toLocaleString(); return v }).reverse() : []"/>
<n-input v-model:value="msgInput" type="textarea" placeholder="发送信息" :autosize="{
minRows: 1,
maxRows: 2
}" @keydown="handleKeyDown" style="margin-top: 0px;">
<template #suffix>
<n-icon :component="DocumentsOutline" style="cursor: pointer;margin-right: 5px;" @click='filePicker({fn:sendFiles,multiple:true})'/>
<n-icon :component="ImagesOutline" style="cursor: pointer;margin-right: 5px;" @click='filePicker({fn:sendImages,accept:"image/*",multiple:true})'/>
<n-icon :component="SendOutline" style="cursor: pointer;" @click='sendMsg(activeGID, msgInput)'/>
</template>
</n-input>
<!-- <n-button @click="sendMsg(activeGID, msg)" :disabled="msg === '' || activeGID === null"
:type="msg === '' || activeGID === null ? 'default' : 'primary'">
发送消息
</n-button> -->
</div>
</n-flex>
<!-- {{ dataTest }} -->
<!-- {{ Messages }} -->
<n-modal v-model:show="showModal">
<n-card
style="width: fit-content;"
title=""
:bordered="false" :bordered="false"
:single-line="false" size="huge"
/> role="dialog"
aria-modal="true"
>
<n-qr-code :value="QRCodeData" style="padding: 0;" :size="256" error-correction-level="Q" :v-if="showModal"
:icon-src='0?QRIconUrl:null'
:icon-size="128"/>
</n-card>
</n-modal>
</template> </template>
<script setup> <script setup>
import {ref} from 'vue' import { ref,onMounted } from 'vue'
import {NText,NDataTable,NButton,NInput} from 'naive-ui' // import { useModal } from "naive-ui";
import useClipboard from "vue-clipboard3";
import { NIcon, NFlex, NDataTable, NButton, NInput, NSelect,NModal,NCard , NQrCode} from 'naive-ui'
import { useSettingStore } from "@/pinia/store"; import { useSettingStore } from "@/pinia/store";
import { generateKey,encryptString } from '@/func/upload'; import { generateKey, encryptString, encrypt } from '@/func/upload';
import { decryptString } from '@/func/download'; import { decryptString } from '@/func/download';
import WebsocketHeartbeatJs from 'websocket-heartbeat-js'; import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
import { CopyOutline,SendOutline,ImagesOutline,DocumentsOutline } from '@vicons/ionicons5'
import getQueryString from '@/func/getQuery'
import chatui from "@/components/chatui.vue"
import { Base64 } from 'js-base64';
import { useMessage, useNotification } from "naive-ui";
import filePicker from "@/func/filePicker"
const message = useMessage()
// const modal = useModal();
const { toClipboard } = useClipboard();
const setting = useSettingStore(); const setting = useSettingStore();
const serverPort = setting.serverPort; const serverPort = setting.serverPort;
const baseUrl = 'ws' + "://" + location.hostname + ":" + serverPort; const wsBaseUrl = 'ws' + "://" + location.hostname + ":" + serverPort;
const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
const QRIconUrl = ref(location.protocol + "//" + location.hostname + ":" + serverPort + "/favicon.ico")
const columns = [ const columns = [
{
"title": "type",
"key": "type",
width: 80
},
{
"title": "from",
"key": "from",
width: 80
},
{
"title": "timestamp",
"key": "timestampString",
width: 150,
// sorter: {
// compare: (a, b) => {
// // console.log(a,b,new Date(a),parseInt(new Date(a)) - parseInt(new Date(b)))
// return parseInt(new Date(a.timestamp)) - parseInt(new Date(b.timestamp))
// },
// }
},
{
"title": "msg",
"key": "msg"
}
]
const columnsTest = [
{ {
title: "from", title: "from",
key: "from" key: "from"
@@ -56,22 +141,52 @@ const columns = [
key: "msg" key: "msg"
}, },
]; ];
const data = ref([]) function handleKeyDown(e){
const gID = ref('') // console.log(e,e.ctrlKey,e.code)
const codeInput = ref('') if(e.ctrlKey === true&&e.code === 'Enter'){
sendMsg(activeGID.value, msgInput.value)
}
}
// 用于二维码分享
const showModal = ref(false)
const QRCodeData = ref('')
function shareWithQR(){
let code = activeGID.value+gID2key[activeGID.value].key
QRCodeData.value = location.protocol + "//" + location.hostname + ":" + serverPort + `/?aP=chat&cG=${code}`
showModal.value = true
}
// 凭此code进入群组
const groupCode = ref('')
// 当前活跃的群组ID
const activeGID = ref(null)
// 临时生成的key,之后与返回的code结合形成groupCode
const key = ref('') const key = ref('')
const msg = ref('')
const joinedGroup = [] // 存储所有消息,通过gID查询一个群组的消息,{msg,from,timestamp}
const Messages = ref({})
const dataTest = ref([])
const gID = ref('')
// const codeInput = ref('')
const msgInput = ref('')
const joinedGroup = ref([])
// 由server随机分配uuid
const uuid = ref('') const uuid = ref('')
const gID2key = {} const gID2key = {}
const options = { const options = {
url: baseUrl, url: wsBaseUrl,
pingTimeout: 20000, pingTimeout: 20000,
pongTimeout: 10000, pongTimeout: 10000,
reconnectTimeout: 3000, reconnectTimeout: 3000,
pingMsg: JSON.stringify({type:'heartbeat'}) pingMsg: JSON.stringify({ type: 'heartbeat' })
} }
let socket = new WebsocketHeartbeatJs(options); let socket = new WebsocketHeartbeatJs(options);
@@ -79,55 +194,66 @@ let socket = new WebsocketHeartbeatJs(options);
// socket.addEventListener("open", (event) => { // socket.addEventListener("open", (event) => {
// // socket.send(""); // // socket.send("");
// }); // });
// setInterval(()=>console.log(activeGID.value),1000)
socket.onmessage= (event) => { socket.onmessage = (event) => {
const msg = JSON.parse(event.data) const msg = JSON.parse(event.data)
data.value.push({from:'server',msg:event.data}) dataTest.value.push({ from: 'server', msg: event.data })
if(msg.type){ if (msg.type) {
let type = msg.type let type = msg.type
if(type === 'sMsg'){ if (type === 'sMsg') {
let type1 = msg.type1 let type1 = msg.type1
if(type1 === 'create'){ if (type1 === 'create') {
gID.value=msg.gID gID.value = msg.gID
}else if(type1 === 'joinVerify'){ groupCode.value = msg.gID + key.value
joinGroup(groupCode.value)
} else if (type1 === 'joinVerify') {
let gID = msg.gID let gID = msg.gID
let key = gID2key[gID].key let key = gID2key[gID].key
// console.log(gID2key,gID,key) let res = decryptString(key, msg.verify)
let res = decryptString(key,msg.verify) // console.log(res, key, msg.verify)
console.log(res,key,msg.verify)
if(res === 'miaoSpring'){ // 验证成功,正式加入群组
socket.send(JSON.stringify({type:'join',type1:'success',gID})) if (res === 'miaoSpring') {
joinedGroup.push(msg.gID) socket.send(JSON.stringify({ type: 'join', type1: 'success', gID }))
joinedGroup.value.push(msg.gID)
Messages.value[msg.gID] = []
// if(activeGID.value === null){
activeGID.value = msg.gID
// }
} }
}else if(type1 === 'history'){ } else if (type1 === 'history') {
let gID = msg.gID let gID = msg.gID
let key = gID2key[gID].key let key = gID2key[gID].key
for(let m of JSON.parse(msg.history)){ Messages.value[msg.gID].length = 0
data.value.push({from:'decrypt',msg:decryptString(key,m.msg)}) for (let m of JSON.parse(msg.history)) {
Messages.value[msg.gID].push({ type: m.type,type1:m.type1, msg: decryptString(key, m.msg), from: m.from, timestamp: m.timestamp })
dataTest.value.push({ from: 'decrypt', msg: decryptString(key, m.msg) })
} }
}else if(type1 === 'uuid'){ } else if (type1 === 'uuid') {
uuid.value = msg.uuid uuid.value = msg.uuid
} }
}else if(type === 'gMsg'){ } else if (type === 'gMsg') {
let gID = msg.gID let gID = msg.gID
let key = gID2key[gID].key let key = gID2key[gID].key
data.value.push({from:'decrypt',msg:decryptString(key,msg.msg)}) Messages.value[msg.gID].push({ type: msg.type,type1:msg.type1, msg: decryptString(key, msg.msg), from: msg.from, timestamp: msg.timestamp })
dataTest.value.push({ from: 'decrypt', msg: decryptString(key, msg.msg) })
} }
} }
}; };
socket.onopen = ()=>{ socket.onopen = () => {
if(uuid.value){ if (uuid.value) {
socket.send(JSON.stringify({type:'uuid',type1:'set',uuid:uuid.value})) socket.send(JSON.stringify({ type: 'uuid', type1: 'set', uuid: uuid.value }))
}else{ } else {
socket.send(JSON.stringify({type:'uuid',type1:'get'})) socket.send(JSON.stringify({ type: 'uuid', type1: 'get' }))
} }
console.log(joinedGroup) // console.log(joinedGroup)
let toJoin = [...joinedGroup] let toJoin = [...joinedGroup.value]
joinedGroup.length = 0 joinedGroup.value.length = 0
for(let gID of toJoin){ for (let gID of toJoin) {
let code = gID+gID2key[gID].key let code = gID + gID2key[gID].key
console.log(code) console.log(code)
joinGroup(code) joinGroup(code)
} }
@@ -137,16 +263,16 @@ socket.onreconnect = (e) => {
console.log('reconnecting...'); console.log('reconnecting...');
} }
function createGroup(){ function createGroup() {
let keyt = generateKey(setting) let keyt = generateKey(setting)
key.value = keyt key.value = keyt
socket.send(JSON.stringify({type:'create',verify:encryptString(keyt,'miaoSpring',setting)})) socket.send(JSON.stringify({ type: 'create', verify: encryptString(keyt, 'miaoSpring', setting) }))
} }
function joinGroup(code){ function joinGroup(code) {
var gID = code.substring(0, 8); var gID = code.substring(0, 8);
var key = code.substring(8); var key = code.substring(8);
if(joinedGroup.includes(gID)){ if (joinedGroup.value.includes(gID)) {
return return
} }
gID2key[gID] = {} gID2key[gID] = {}
@@ -154,18 +280,99 @@ function joinGroup(code){
joinGroup_Verify(gID) joinGroup_Verify(gID)
} }
function joinGroup_Verify(gID){ function joinGroup_Verify(gID) {
socket.send(JSON.stringify({type:'join',type1:'verify',gID})) socket.send(JSON.stringify({ type: 'join', type1: 'verify', gID }))
} }
function sendMsg(gID,msg){ function sendMsg(gID, msg) {
if(msg === '' || msg === null || activeGID.value === null)return
let key = gID2key[gID].key let key = gID2key[gID].key
msg = encryptString(key,msg,setting) msg = encryptString(key, msg, setting)
socket.send(JSON.stringify({type:'message',msg,gID})) socket.send(JSON.stringify({ type: 'message',type1:'text', msg, gID }))
msgInput.value = null
} }
// async function encryptImg(file){
// var f = new Blob([file]);
// let key = gID2key[gID].key
// let eU8A = await encrypt(key, f, fileName, setting)
// let eb = Base64.fromUint8Array(eU8A)
// return eb
// }
async function uploadFile(file){
if(activeGID.value === null)return
var fileName = file.name;
let key = gID2key[activeGID.value].key
const encryptedFileName = encryptString(key, fileName, setting);
var f = new Blob([file]);
let data = await encrypt(key, f, fileName, setting)
const params = {
fileName: encryptedFileName,
downloadCredit:-1
};
message.info(`开始上传${fileName}`)
const xhr = new XMLHttpRequest();
// onProgress({ percent: 1 });
xhr.open(
"POST",
baseUrl + "file/upload?" + new URLSearchParams(params)
);
// console.log(setting.token)
xhr.setRequestHeader('Authorization', `Bearer ${setting.token}`);
xhr.send(data);
const code = await new Promise((resolve,reject)=>{
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
let id = xhr.responseText;
let code = id + key
message.success(`上传${fileName}成功`)
resolve(code)
} else {
if(xhr.statusText !== 'OK'){
console.error(xhr.statusText);
message.error(`上传${fileName}失败`)
}
}
};
})
console.log(code)
return code
}
async function sendImages(files){
if(activeGID.value === null || files.length === 0)return
for(let file of files){
const code = await uploadFile(file)
let key = gID2key[activeGID.value].key
socket.send(JSON.stringify({ type: 'message',type1:'img', msg: encryptString(key, code), gID:activeGID.value }))
};
}
async function sendFiles(files){
if(activeGID.value === null || files.length === 0)return
for(let file of files){
let fileName = file.name;
const code = await uploadFile(file)
let key = gID2key[activeGID.value].key
socket.send(JSON.stringify({ type: 'message',type1:'file', msg: encryptString(key, code), gID:activeGID.value }))
};
}
onMounted(()=>{
let aP = getQueryString('aP')
let cG = getQueryString('cG')
if(aP === 'chat' && cG){
groupCode.value = cG
var st = setInterval(()=>{
if(joinedGroup.value.includes(cG.substring(0,8))){
clearInterval(st)
return
}
joinGroup(cG)
},2000)
}
})
</script> </script>
<style> <style></style>
</style>
+6 -2
View File
@@ -28,7 +28,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted,computed } from "vue";
import { useSettingStore } from "@/pinia/store"; import { useSettingStore } from "@/pinia/store";
import {decrypt, decryptString, downloadFromUint8Array} from '../func/download' import {decrypt, decryptString, downloadFromUint8Array} from '../func/download'
import {NInput,NFlex,NDataTable,NButton} from 'naive-ui' import {NInput,NFlex,NDataTable,NButton} from 'naive-ui'
@@ -158,7 +158,11 @@ const downloadFile = code => {
fileName: id fileName: id
}; };
const options = { const options = {
method: "GET" method: "GET",
headers: {
'Authorization': `Bearer ${setting.token}`
// 'Content-Type':'application/json'
},
}; };
fetch(baseUrl + "file/download?" + new URLSearchParams(params), options) fetch(baseUrl + "file/download?" + new URLSearchParams(params), options)
.then(res => { .then(res => {
+90
View File
@@ -0,0 +1,90 @@
<template>
<n-card style="box-shadow: 10px 10px 5px #888888;margin-top: 100px; padding-left: 25px;padding-right: 25px; padding-bottom: 30px" size="small">
<n-flex vertical justify="center">
<n-flex justify="center">
<h1 style="letter-spacing:25px;font-size: 40px;">登录</h1>
</n-flex>
<n-input-group>
<n-input-group-label>账号</n-input-group-label>
<n-input placeholder="userID" v-model:value="userIDInput" :loading="isLogining" :disabled="isLogining"></n-input>
</n-input-group>
<n-input-group>
<n-input-group-label>密码</n-input-group-label>
<n-input placeholder="userPassword" v-model:value="userPasswordInput" type="password"
show-password-on="click" :loading="isLogining" :disabled="isLogining"></n-input>
</n-input-group>
<n-flex justify="end">
<n-flex style="flex:1;align-items: center;">
<n-checkbox size="large" label="保持登录状态" v-model:checked="keepTokenLocal" />
</n-flex>
<n-flex justify="end">
<n-button @click="useGuestAccount" v-if="settingData.enableGuest">使用访客账号登录</n-button>
<n-button @click="login">登录</n-button>
</n-flex>
</n-flex>
</n-flex>
</n-card>
</template>
<script setup>
import { computed, ref } from 'vue'
import { NCheckbox, NInputGroupLabel, NInputGroup, NP, NFlex, NDataTable, NButton, NInput, NCard } from 'naive-ui'
import { useSettingStore } from "@/pinia/store";
import { useMessage, useNotification } from "naive-ui";
import sha256 from "crypto-js/sha256.js";
const message = useMessage()
const settingData = useSettingStore();
const serverPort = settingData.serverPort;
const userIDInput = ref('')
const userPasswordInput = ref('')
const isLogining = ref(false)
const baseUrl =
location.protocol + "//" + location.hostname + ":" + serverPort + "/";
const Token = computed(() => settingData.token)
const keepTokenLocal = ref(true)
function useGuestAccount() {
userIDInput.value = 'guest'
userPasswordInput.value = 'guest'
login()
}
async function login() {
try {
isLogining.value = true
const response = await fetch(`${baseUrl}user/login`, {
method: 'POST',
headers: {
// 'Authorization': `Bearer ${Token}`
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: userIDInput.value,
pwd: sha256(userPasswordInput.value).toString()
})
})
let data = await response.json()
if (data.status === 'Error') {
console.log('error')
throw ('failed')
}
console.log(data.token, data)
settingData.setJWT(data.token,keepTokenLocal)
settingData.setUserID(userIDInput.value,keepTokenLocal)
settingData.setUserGroup(data.userGroup,keepTokenLocal)
message.success(`登录成功`);
} catch (e) {
isLogining.value = false
message.error(`登录失败`);
console.log('failed')
console.log(e)
}
}
</script>
+4 -2
View File
@@ -14,7 +14,7 @@
<n-text style="font-size: 16px;user-select: none;">点击此处或者拖动文件到该区域来上传</n-text> <n-text style="font-size: 16px;user-select: none;">点击此处或者拖动文件到该区域来上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0;user-select: none;"> <n-p depth="3" style="margin: 8px 0 0 0;user-select: none;">
最大支持200MB大小文件,其实只是懒得做分片上传而已 最大支持200MB大小文件,其实只是懒得做分片上传而已
<br />虽然理论上没有人有能力审查你上传的东西 <!-- <br />虽然理论上没有人有能力审查你上传的东西 -->
<br />反正没人用写这么多干什么。。。 <br />反正没人用写这么多干什么。。。
</n-p> </n-p>
</n-upload-dragger> </n-upload-dragger>
@@ -37,7 +37,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted,computed } from "vue";
// import aesjs from "aes-js"; // import aesjs from "aes-js";
// import { Base64 } from "js-base64"; // import { Base64 } from "js-base64";
import { useMessage, useNotification } from "naive-ui"; import { useMessage, useNotification } from "naive-ui";
@@ -204,6 +204,8 @@ const customRequest = ({
"POST", "POST",
baseUrl + "file/upload?" + new URLSearchParams(params) baseUrl + "file/upload?" + new URLSearchParams(params)
); );
console.log(setting.token)
xhr.setRequestHeader('Authorization', `Bearer ${setting.token}`);
xhr.send(data); xhr.send(data);
xhr.onreadystatechange = () => { xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
+17
View File
@@ -0,0 +1,17 @@
import jwt from 'jsonwebtoken'
import sha256 from 'crypto-js/sha256.js';
import fs from 'fs'
const JWT_SK = fs.readFileSync('JWT_SK.key').toString()
let login = {id:123456,pwd:sha256('nmsl').toString()}
console.log(JWT_SK,login)
let token = jwt.sign(login,JWT_SK,{ expiresIn: '1h' })
let fakeToken = `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTIzNDU2LCJwd2QiOiIwNmYxYTIzOTE2MGZhNTQ2ZTAyOWM5YWQxNTU4OGYwYTI0YWVjYWNhN2M1ZjkyZTE4YmFlNjMzMDk1ZmRjM2JlIiwiaWF0IjoxNzExNTEzMjIwLCJleHAiOjE3MTE1MTY4MjB9.O4-0DkT8Nn0jwZUj09DFK-dOhOfG0n1Mmx3QvsfU8x1`
// error
let verify = jwt.verify(token,JWT_SK)
console.log(token,verify,parseInt(Date.now()/1000))
+21
View File
@@ -18,4 +18,25 @@ export default defineConfig({
port:'17058', port:'17058',
open:true, open:true,
}, },
esbuild: {
drop: ['console', 'debugger'],
},
build: {
minify: true,
rollupOptions: {
output: {
// 打包输出的配置
manualChunks: id => {
// 这个ID,就是所有文件的绝对路径
if (id.includes("node_modules")) {
// 因为 node_modules 中的依赖通常是不会改变的
// 所以直接单独打包出去
// 这个return 的值就是打包的名称
// return id.split('node_modules')[1]
return "modules";
}
}
}
}
}
}) })