Compare commits

...
23 Commits
Author SHA1 Message Date
539943419 82d9531c25 ... 2024-07-29 15:20:30 +08:00
539943419 ad8260b87e app上路径直接写死 2024-07-29 15:06:07 +08:00
539943419 4b9599ef8a fix 2024-07-29 15:00:05 +08:00
539943419 5ad3f87323 fix 2024-07-29 14:58:13 +08:00
539943419 5b5849744b ignore visualizer.html 2024-07-29 14:43:06 +08:00
539943419 44f3d57884 统一在store.js中管理baseUrl 2024-07-29 14:41:50 +08:00
539943419 dfc26aedea delete visualizer.html 2024-07-20 21:18:03 +08:00
539943419 1780a0bb8b 添加复制时提示 2024-07-20 21:16:10 +08:00
539943419 cf4297cc3c 修改chat界面样式,增加复制文本消息功能 2024-07-20 09:16:49 +08:00
539943419 b29add4ae5 修正SM4解密时产生错误结果的bug 2024-07-19 19:59:16 +08:00
539943419 f0e4a777d9 完善使用worker进行加密解密相关代码 2024-07-19 17:24:08 +08:00
539943419 3c2dc0f183 使用worker进行加解密,防止主线程卡死 2024-07-19 17:04:54 +08:00
539943419 8d08392cad 修复了chat中图片显示的相关问题 2024-07-07 12:40:25 +08:00
539943419 ba43ad2bbc 添加token过期检查 2024-07-02 20:13:07 +08:00
539943419 84025a83ab 优化vite配置 2024-07-02 19:27:26 +08:00
539943419 17caf491f1 create miaoworkerpool.js 2024-06-29 01:06:00 +08:00
539943419 133d0b7844 现在非开发环境下web端视为服务器端口与页面端口一致 2024-04-23 20:18:45 +08:00
539943419 5ca7125328 chat中可通过ctrl+enter发送消息 2024-04-03 13:00:18 +08:00
539943419 0389c8b7fe 优化细节表现 2024-04-03 12:45:13 +08:00
539943419 dd56403d93 chat中添加上传文件功能 2024-04-03 11:33:46 +08:00
539943419 709cac04f6 更新chat的ui界面 2024-04-02 23:18:54 +08:00
539943419 530e54a596 添加登录功能 2024-03-27 22:05:00 +08:00
539943419 20e9f4940e 修正AES加密字符串中对emoji处理的编码问题 2024-03-22 22:58:12 +08:00
31 changed files with 4108 additions and 345 deletions

No files matched your search

+7
View File
@@ -32,3 +32,10 @@ coverage
# files created with miaoShare service # files created with miaoShare service
temp temp
miaoShare.db miaoShare.db
# addUser.js
# removeUser.js
# JWT SK
*.key
visualizer.html
+16
View File
@@ -0,0 +1,16 @@
import sha256 from 'crypto-js/sha256.js';
import Database from "better-sqlite3";
const dbPath = "./miaoShare.db";
const db = new Database(dbPath, { verbose: null });
const stmt_insertId2FileName = db.prepare(`INSERT INTO USERS (user_id, password_hash, user_group, uploadFilesSizeCount) VALUES (?, ?, ?, 0)`);
let userList = [
['539943419','zjh030628','admin'],
// ['guest','guest','guest']
].map(a=>{a[1]=sha256(a[0]+sha256(a[1]).toString()).toString();return a})
for(let i of userList){
stmt_insertId2FileName.run(...i)
}
+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",
+8 -2
View File
@@ -16,8 +16,11 @@
"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",
"rollup-plugin-visualizer": "^5.12.0",
"sm-crypto": "^0.3.13", "sm-crypto": "^0.3.13",
"uuid": "^9.0.1", "uuid": "^9.0.1",
"vicons": "^0.0.1", "vicons": "^0.0.1",
@@ -25,15 +28,18 @@
"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",
"sass": "^1.77.8",
"vfonts": "^0.0.3", "vfonts": "^0.0.3",
"vite": "^5.0.11" "vite": "^5.0.11"
} }
+2386
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

+15
View File
@@ -0,0 +1,15 @@
import sha256 from 'crypto-js/sha256.js';
import Database from "better-sqlite3";
const dbPath = "./miaoShare.db";
const db = new Database(dbPath, { verbose: null });
const stmt_ = db.prepare(`DELETE FROM USERS WHERE user_id = ?`);
let userList = [
['admin'],["guest"]
]
for(let i of userList){
stmt_.run(...i)
}
+244 -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,26 @@ 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");
// 屏蔽console.log
console.log=()=>{}
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 +39,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 +88,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 +136,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 +159,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 +194,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 +220,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 +271,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 +375,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 +400,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 +424,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 +464,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 +473,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 +502,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 +516,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 +554,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 +587,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;
} }
} }
} }
+115 -44
View File
@@ -3,87 +3,157 @@ 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 = settingData.baseUrl
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(() => {
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 +175,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;
+320
View File
@@ -0,0 +1,320 @@
<template>
<!-- <div style="border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;"> -->
<n-scrollbar class="scrollbar">
<n-image-group>
<div v-for="msg of props.messages">
<div class="message-title">{{ `${msg.from.split('-')[0]} ${msg.timestampString}` }}</div>
<div class="message-text" v-if="msg.type1 === 'text'">
<span>{{ msg.msg }}</span>
<n-icon :component="CopyOutline" class="message-text-copy" @click='_toClipboard(msg.msg)' />
</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'" class="message-file">
<n-flex class="message-file-content" @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, CopyOutline } from '@vicons/ionicons5'
import fileSize2String from '@/func/fileSize2String'
import useClipboard from "vue-clipboard3";
import { useMessage } from "naive-ui";
const message = useMessage()
const { toClipboard } = useClipboard();
const _toClipboard = async (text) => {
await toClipboard(text)
message.info(`已复制 ${text.length < 10 ? text : text.substring(0,7) + '...'}`)
}
const setting = useSettingStore();
const serverPort = setting.serverPort;
const baseUrl = setting.baseUrl
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 scoped lang="scss">
.scrollbar {
padding: 10px;
padding-right: 15px;
border: 1px solid rgb(239, 239, 245);
border-radius: 5px;
position: relative;
.message-title {
color: slategrey;
}
.message-text {
position: relative;
background-color: rgb(239, 239, 245);
padding: 2px 7px;
// padding-right: 5px;
width: fit-content;
max-width: calc(100% - 28px);
border-radius: 5px;
white-space: pre-wrap;
.message-text-copy {
cursor: pointer;
position: absolute;
bottom: 8px;
right: -10px;
height: 7px;
width: 7px;
opacity: 0;
}
}
.message-text:hover .message-text-copy {
opacity: 1;
transition: opacity 0.3s ease-in-out;
}
.message-text:not(:hover) .message-text-copy {
opacity: 0;
transition: opacity 0.3s ease-in-out;
transition-delay: 0.5s;
}
.message-file {
background-color: rgb(239, 239, 245);
padding: 2px 7px;
width: fit-content;
border-radius: 5px;
.message-file-content {
justify-content: center;
align-items: center;
user-select: none;
cursor: pointer;
}
}
}
</style>
+13
View File
@@ -0,0 +1,13 @@
import aesjs from 'aes-js'
import aes_worker from '@/worker/aes_ctr?worker'
export default (key, data) => {
key = aesjs.utils.utf8.toBytes(key)
return new Promise((resolve) => {
const aesWorker = new aes_worker()
aesWorker.onmessage = (event) => {
resolve(event.data)
}
aesWorker.postMessage({ data, key })
})
}
+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
+27
View File
@@ -0,0 +1,27 @@
import sm4_encrypt_worker from '@/worker/sm4_encrypt?worker'
import sm4_decrypt_worker from '@/worker/sm4_decrypt?worker'
const SM4 = {
encrypt: async (key, data) => {
const encryptedBytes = await new Promise((resolve) => {
const sm4Worker = new sm4_encrypt_worker()
sm4Worker.onmessage = (event) => {
resolve(event.data)
}
sm4Worker.postMessage({ data, key })
})
return encryptedBytes
},
decrypt: async (key, data) => {
const decryptedBytes = await new Promise((resolve) => {
const sm4Worker = new sm4_decrypt_worker()
sm4Worker.onmessage = (event) => {
resolve(event.data)
}
sm4Worker.postMessage({ data, key })
})
return decryptedBytes
}
}
export default SM4
+34 -14
View File
@@ -2,6 +2,8 @@ import aesjs from "aes-js";
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import { saveAs } from "file-saver"; import { saveAs } from "file-saver";
import { sm4 } from "sm-crypto"; import { sm4 } from "sm-crypto";
import AES from './AES'
import SM4 from './SM4'
const blob2Uint8Array = function (blob) { const blob2Uint8Array = function (blob) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -19,7 +21,7 @@ const blob2Uint8Array = function (blob) {
}; };
const decrypt = (key, data) => { const decrypt = (key, data) => {
key = key.trim() key = key.trim()
return blob2Uint8Array(data).then(data => { return blob2Uint8Array(data).then(async data => {
// 无加密 // 无加密
if (key.length === 0) { if (key.length === 0) {
return data; return data;
@@ -28,20 +30,23 @@ const decrypt = (key, data) => {
if (key.length === 19) { if (key.length === 19) {
key = [...aesjs.utils.utf8.toBytes(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" }));
let decryptedBytes = await SM4.decrypt(key, data)
return decryptedBytes; return decryptedBytes;
} else { } else {
// AES // AES
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);
return decryptedBytes; // return decryptedBytes;
const decryptedBytes = await AES(key, data)
return decryptedBytes
} }
}); });
}; };
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 +59,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;
}
+121 -94
View File
@@ -1,111 +1,138 @@
import aesjs from "aes-js"; import aesjs from "aes-js";
import { Base64 } from "js-base64"; import { Base64 } from "js-base64";
import { sm4 } from 'sm-crypto' import { sm4 } from "sm-crypto";
import AES from "./AES";
import SM4 from "./SM4";
// const setting = useSettingStore(); // const setting = useSettingStore();
// const message = useMessage(); // const message = useMessage();
// 生成密钥 // 生成密钥
const generateKey = function(setting) { const generateKey = function (setting) {
if(setting.activeFuction === "不加密"){ if (setting.activeFuction === "不加密") {
return '' return "";
} }
var charMap = "" var charMap = "";
for(let i of setting.activeKeyMap){ for (let i of setting.activeKeyMap) {
if(i === 'A-Z'){ if (i === "A-Z") {
charMap = charMap + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' charMap = charMap + "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}else if(i === "a-z"){ } else if (i === "a-z") {
charMap = charMap + 'abcdefghijklmnopqrstuvwxyz' charMap = charMap + "abcdefghijklmnopqrstuvwxyz";
}else if(i === '0-9'){ } else if (i === "0-9") {
charMap = charMap + '0123456789' charMap = charMap + "0123456789";
} }
} }
var returnStr = "", var returnStr = "",
length = setting.activeFuction !== "AES256" ? 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 = parseInt(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"){ if (setting.activeFuction === "SM4") {
returnStr = 'SM4' + returnStr returnStr = "SM4" + returnStr;
} }
// console.log(returnStr,returnStr.length) // console.log(returnStr,returnStr.length)
return returnStr; return returnStr;
}; };
const blob2Uint8Array = function(blob) { const blob2Uint8Array = function (blob) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let reader = new FileReader(); let reader = new FileReader();
reader.readAsArrayBuffer(blob); reader.readAsArrayBuffer(blob);
reader.onload = function() { reader.onload = function () {
let buffer = reader.result; let buffer = reader.result;
let uint8array = new Uint8Array(buffer); let uint8array = new Uint8Array(buffer);
resolve(uint8array); resolve(uint8array);
}; };
reader.onerror = function() { reader.onerror = function () {
reject(reader.error); reject(reader.error);
}; };
}); });
}; };
const AES = (key,data)=>{
key = aesjs.utils.utf8.toBytes(key); // const AES = (key, data) => {
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); // key = aesjs.utils.utf8.toBytes(key)
var encryptedBytes = aesCtr.encrypt(data); // // var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5))
return encryptedBytes; // // var encryptedBytes = aesCtr.encrypt(data)
} // // return encryptedBytes
const SM4 = (key,data)=>{ // return new Promise((resolve) => {
// console.log(key) // const aesWorker = new aes_worker()
var encryptedBytes = sm4.encrypt(data,key,{padding: 'pkcs#7',output:'array'}); // aesWorker.onmessage = (event) => {
// console.log(encryptedBytes, sm4.decrypt(encryptedBytes, key,{output:'array'})) // resolve(event.data)
return new Uint8Array(encryptedBytes); // }
} // aesWorker.postMessage({ data, key })
// })
// }
// const SM4 = (key, data) => {
// // console.log(key)
// var encryptedBytes = sm4.encrypt(data, key, {
// padding: 'pkcs#7',
// output: 'array'
// })
// // console.log(encryptedBytes, sm4.decrypt(encryptedBytes, key,{output:'array'}))
// return new Uint8Array(encryptedBytes)
// }
const encrypt = (key, data, fileName, setting, message) => { const encrypt = (key, data, fileName, setting, message) => {
// if(!encryptFunction){ // if(!encryptFunction){
// encryptFunction = setting.activeFuction // encryptFunction = setting.activeFuction
// } // }
let encryptFunction = {0:'不加密',16:'AES128',19:'SM4',32:'AES256'}[key.length] let encryptFunction = {
return blob2Uint8Array(data).then(data => { 0: "不加密",
let encrypted_data = null 16: "AES128",
if(encryptFunction === "不加密"|| key === ''){ 19: "SM4",
encrypted_data = data 32: "AES256"
// AES }[key.length];
}else if(encryptFunction != "SM4"){ return blob2Uint8Array(data).then(async data => {
message.info(`开始加密${fileName}`); let encrypted_data = null;
encrypted_data = AES(key,data) if (encryptFunction === "不加密" || key === "") {
message.info(`${fileName}加密完成`); encrypted_data = data;
// SM4 // AES
}else{ } else if (encryptFunction != "SM4") {
message.info(`开始加密${fileName}`); if (message) message.info(`开始加密${fileName}`);
key = [...aesjs.utils.utf8.toBytes(key.substring(3))]; encrypted_data = await AES(key, data);
encrypted_data = SM4(key,data) if (message) message.info(`${fileName}加密完成`);
message.info(`${fileName}加密完成`); // SM4
} } else {
return encrypted_data if (message) message.info(`开始加密${fileName}`);
}); key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
encrypted_data = await SM4.encrypt(key, data);
if (message) message.info(`${fileName}加密完成`);
}
console.log(encrypted_data);
return encrypted_data;
});
}; };
// setting为pinia存储的setting。。。 // setting为pinia存储的setting。。。
const encryptString = (key, str, setting) => { const encryptString = (key, str, setting) => {
// if(!encryptFunction){ // if(!encryptFunction){
// encryptFunction = setting.activeFuction // encryptFunction = setting.activeFuction
// } // }
// console.log(key,key.length) // console.log(key,key.length)
let encryptFunction = {0:'不加密',16:'AES128',19:'SM4',32:'AES256'}[key.length] let encryptFunction = {
if(encryptFunction === "不加密" || key === ''){ 0: "不加密",
// return sm4.encrypt(str, 'miaospringmiaospringmiaospringao') 16: "AES128",
return Base64.encode(str) 19: "SM4",
}else if(encryptFunction !== "SM4"){ 32: "AES256"
key = aesjs.utils.utf8.toBytes(key); }[key.length];
var data = aesjs.utils.utf8.toBytes(str); if (encryptFunction === "不加密" || key === "") {
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5)); // return sm4.encrypt(str, 'miaospringmiaospringmiaospringao')
var encryptedBytes = aesCtr.encrypt(data); return Base64.encode(str);
return Base64.encode(encryptedBytes); } else if (encryptFunction !== "SM4") {
}else{ key = aesjs.utils.utf8.toBytes(key);
key = [...aesjs.utils.utf8.toBytes(key.substring(3))]; // var data = aesjs.utils.utf8.toBytes(str);
// console.log(key) var data = new TextEncoder().encode(str);
return sm4.encrypt(str, key) var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
} var encryptedBytes = aesCtr.encrypt(data);
var res = Base64.fromUint8Array(encryptedBytes, true);
// console.log(encryptedBytes,res)
return res;
} else {
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
// console.log(key)
return sm4.encrypt(str, key);
}
}; };
export {encrypt,encryptString,generateKey} export { encrypt, encryptString, generateKey };
+74 -21
View File
@@ -1,31 +1,84 @@
import { defineStore } from "pinia"; import { defineStore } from 'pinia'
import config from "../../miaoShareConfig.json" import config from '../../miaoShareConfig.json'
console.log(location)
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')
var activeFuctionLocal = localStorage.getItem('activeFuction') var activeFuctionLocal = localStorage.getItem('activeFuction')
if (activeFuctionLocal == null){ if (activeFuctionLocal == null) {
activeFuctionLocal = 'AES128' activeFuctionLocal = 'AES128'
localStorage.setItem('activeFuction',activeFuctionLocal) localStorage.setItem('activeFuction', activeFuctionLocal)
} }
// localStorage.removeItem('activeKeyMap') // localStorage.removeItem('activeKeyMap')
var activeKeyMap = localStorage.getItem('activeKeyMap') var activeKeyMap = localStorage.getItem('activeKeyMap')
if (activeKeyMap == null ){ 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)
} }
export const useSettingStore = defineStore("setting", {
state: () => ({ // 开发环境下服务器端口使用设置的端口,否则使用与页面相同的端口
const serverPort =
process.env.NODE_ENV === 'development' ? config.PORT : location.port
const baseUrl = 'http://110.238.117.95:17057/'
// location.protocol + '//' + location.hostname + ':' + serverPort + '/'
// const wsBaseUrl = 'ws' + '://' + location.hostname + ':' + serverPort
const wsBaseUrl = 'ws://110.238.117.95:17057/'
// console.log(process.env.NODE_ENV,config.DEVPORT,location.port,serverPort)
export const useSettingStore = defineStore('setting', {
state: () => ({
activeFuction: activeFuctionLocal, activeFuction: activeFuctionLocal,
activeKeyMap:JSON.parse(activeKeyMap), activeKeyMap: JSON.parse(activeKeyMap),
serverPort:config.PORT enableGuest: config.enableGuest,
}), serverPort,
getters: { token,
// double: state => state.count * 2 userID,
}, userGroup,
actions: { baseUrl,
// increment() { wsBaseUrl
// this.count++; }),
// } getters: {
} // double: state => state.count * 2
}); },
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() {
// 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>
+297 -83
View File
@@ -1,52 +1,144 @@
<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 _toClipboard = async (text) => {
await toClipboard(text)
message.info(`已复制 ${text.length < 10 ? text : text.substring(0,7) + '...'}`)
}
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 wsBaseUrl = setting.wsBaseUrl
// const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
const baseUrl = setting.baseUrl
const QRIconUrl = ref(baseUrl + "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 +148,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 +201,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 +270,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 +287,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>
+8 -4
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'
@@ -40,8 +40,8 @@ const setting = useSettingStore();
// 服务端端口 // 服务端端口
const serverPort = setting.serverPort; const serverPort = setting.serverPort;
var baseUrl = // const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
location.protocol + "//" + location.hostname + ":" + serverPort + "/"; const baseUrl = setting.baseUrl
// console.log(baseUrl); // console.log(baseUrl);
// const blob2Uint8Array = function(blob) { // const blob2Uint8Array = function(blob) {
@@ -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 baseUrl = settingData.baseUrl
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>
+8 -5
View File
@@ -14,12 +14,12 @@
<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>
</n-upload> </n-upload>
<n-flex justify="end" style="margin: 10px 0;position:relative"> <n-flex justify="end" style="margin: 10px 0;position:relative">
<n-input-number v-model:value="downloadCredit" placeholder="可下载次数,默认无限" clearable :min="1" :max="2147483647" style="width:max-content"/> <n-input-number v-model:value="downloadCredit" placeholder="可下载次数,默认无限" clearable :min="1" :max="2147483647" style="width:max-content"/>
<n-button type="primary" @click="clickUpload">开始上传</n-button> <n-button type="primary" @click="clickUpload">开始上传</n-button>
@@ -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";
@@ -94,8 +94,9 @@ var file_Code_Map = ref([]);
var uploadProcess = ref(0); var uploadProcess = ref(0);
const baseUrl = // const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
location.protocol + "//" + location.hostname + ":" + serverPort + "/"; const baseUrl = setting.baseUrl
console.log(baseUrl)
// const blob2Uint8Array = function(blob) { // const blob2Uint8Array = function(blob) {
@@ -204,6 +205,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) {
+9
View File
@@ -0,0 +1,9 @@
import aesjs from 'aes-js'
self.onmessage = async function (event) {
const data = event.data.data
const key = event.data.key
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(7058))
const encryptData = aesCtr.encrypt(data)
self.postMessage(encryptData)
}
+9
View File
@@ -0,0 +1,9 @@
import { sm4 } from 'sm-crypto'
self.onmessage = async function (event) {
const data = event.data.data
const key = event.data.key
self.postMessage(
new Uint8Array(sm4.decrypt([...data], key, { output: 'array' }))
)
}
+11
View File
@@ -0,0 +1,11 @@
import { sm4 } from 'sm-crypto'
self.onmessage = async function (event) {
const data = event.data.data
const key = event.data.key
const encryptData = sm4.encrypt(data, key, {
padding: 'pkcs#7',
output: 'array'
})
self.postMessage(new Uint8Array(encryptData))
}
+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))
+44 -17
View File
@@ -1,21 +1,48 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from "node:url";
import { visualizer } from "rollup-plugin-visualizer";
import { defineConfig } from 'vite' import { defineConfig } from "vite";
import vue from '@vitejs/plugin-vue' import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
], visualizer({
resolve: { open: true,
alias: { filename: 'visualizer.html' //分析图生成的文件名
'@': fileURLToPath(new URL('./src', import.meta.url)) })
} ],
}, resolve: {
server:{ alias: {
// host:'0.0.0.0', '@': fileURLToPath(new URL('./src', import.meta.url))
port:'17058', }
open:true, },
}, server: {
}) // host:'0.0.0.0',
port: '17058',
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'
}
}
}
}
},
base: './'
})