diff --git a/server.js b/server.js index 04bfbac..dccb573 100644 --- a/server.js +++ b/server.js @@ -53,6 +53,7 @@ CREATE TABLE IF NOT EXISTS USERS( );`); 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_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 = ?`); @@ -78,6 +79,30 @@ const db_getFileName = id => { 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 => { try { @@ -237,6 +262,28 @@ 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){ @@ -318,6 +365,20 @@ app.get("/file/download", verifyToken, 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(){ // console.log(app) @@ -442,10 +503,10 @@ function initMsgHistory(gID) { let verify = stmt_getGroupVerify.get(gID).verify; // 暂时这么写 let msgHistory = db - .prepare(`SELECT message,type,sender,timestamp FROM ${gID}`) + .prepare(`SELECT message,type,type1,sender,timestamp FROM ${gID}`) .all() .map(v => { - return { type: v.type, msg: v.message, from: v.sender, timestamp: v.timestamp }; + return { type: v.type,type1:v.type1, msg: v.message, from: v.sender, timestamp: v.timestamp }; }); // console.log(msgHistory) groupsMap[gID] = { verify, member: [], msgHistory }; diff --git a/src/App.vue b/src/App.vue index 39d6cc8..59676bf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,11 +10,12 @@ import { NNotificationProvider, NMessageProvider, NCard, NMenu, NIcon,NModalProv import { ref, watch, h ,shallowRef, onMounted, computed} from "vue"; // import { useRoute } from "vue-router"; import { - ArrowUpCircleOutline as uploadIcon, - ArrowDownCircleOutline as downloadIcon, - ChatboxEllipsesOutline as chatIcon, + CloudUploadOutline as uploadIcon, + CloudDownloadOutline as downloadIcon, + // ChatboxEllipsesOutline as chatIcon, + ChatbubblesOutline as chatIcon, SettingsOutline as settingIcon, - PersonOutline as AccountIcon + FootstepsOutline as AccountIcon } from '@vicons/ionicons5' import { useSettingStore } from "@/pinia/store"; diff --git a/src/components/chatui.vue b/src/components/chatui.vue index 7e30103..e1ef0b1 100644 --- a/src/components/chatui.vue +++ b/src/components/chatui.vue @@ -1,29 +1,262 @@ \ No newline at end of file diff --git a/src/func/filePicker.js b/src/func/filePicker.js new file mode 100644 index 0000000..8ef3844 --- /dev/null +++ b/src/func/filePicker.js @@ -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(); +} diff --git a/src/func/fileSize2String.js b/src/func/fileSize2String.js new file mode 100644 index 0000000..c43efd2 --- /dev/null +++ b/src/func/fileSize2String.js @@ -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' +} \ No newline at end of file diff --git a/src/func/upload.js b/src/func/upload.js index 3fe9c07..608d3b1 100644 --- a/src/func/upload.js +++ b/src/func/upload.js @@ -73,15 +73,19 @@ const encrypt = (key, data, fileName, setting, message) => { encrypted_data = data // AES }else if(encryptFunction != "SM4"){ - message.info(`开始加密${fileName}`); + if(message) + message.info(`开始加密${fileName}`); encrypted_data = AES(key,data) - message.info(`${fileName}加密完成`); + if(message) + message.info(`${fileName}加密完成`); // SM4 }else{ - message.info(`开始加密${fileName}`); + if(message) + message.info(`开始加密${fileName}`); key = [...aesjs.utils.utf8.toBytes(key.substring(3))]; encrypted_data = SM4(key,data) - message.info(`${fileName}加密完成`); + if(message) + message.info(`${fileName}加密完成`); } return encrypted_data }); diff --git a/src/views/chat.vue b/src/views/chat.vue index 6867d24..2f909c9 100644 --- a/src/views/chat.vue +++ b/src/views/chat.vue @@ -38,12 +38,14 @@ - + }" @keydown="handleKeyDown" style="margin-top: 0px;"> @@ -53,6 +55,8 @@ --> + + @@ -77,21 +81,27 @@ import { ref,onMounted } from 'vue' import useClipboard from "vue-clipboard3"; import { NIcon, NFlex, NDataTable, NButton, NInput, NSelect,NModal,NCard , NQrCode} from 'naive-ui' import { useSettingStore } from "@/pinia/store"; -import { generateKey, encryptString } from '@/func/upload'; +import { generateKey, encryptString, encrypt } from '@/func/upload'; import { decryptString } from '@/func/download'; import WebsocketHeartbeatJs from 'websocket-heartbeat-js'; -import { CopyOutline,SendOutline } from '@vicons/ionicons5' +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 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 = [ @@ -160,7 +170,7 @@ const Messages = ref({}) const dataTest = ref([]) const gID = ref('') // const codeInput = ref('') -const msg = ref('') +const msgInput = ref('') const joinedGroup = ref([]) // 由server随机分配uuid @@ -170,7 +180,7 @@ const uuid = ref('') const gID2key = {} const options = { - url: baseUrl, + url: wsBaseUrl, pingTimeout: 20000, pongTimeout: 10000, reconnectTimeout: 3000, @@ -216,7 +226,7 @@ socket.onmessage = (event) => { let key = gID2key[gID].key Messages.value[msg.gID].length = 0 for (let m of JSON.parse(msg.history)) { - Messages.value[msg.gID].push({ type: m.type, msg: decryptString(key, m.msg), from: m.from, timestamp: m.timestamp }) + 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') { @@ -226,7 +236,6 @@ socket.onmessage = (event) => { let gID = msg.gID let key = gID2key[gID].key Messages.value[msg.gID].push({ type: msg.type,type1:msg.type1, msg: decryptString(key, msg.msg), from: msg.from, timestamp: msg.timestamp }) - // console.log(Messages.value) dataTest.value.push({ from: 'decrypt', msg: decryptString(key, msg.msg) }) } } @@ -278,25 +287,75 @@ function sendMsg(gID, msg) { let key = gID2key[gID].key msg = encryptString(key, msg, setting) socket.send(JSON.stringify({ type: 'message',type1:'text', msg, gID })) + msgInput.value = null } -function sendImg(gID,code){ - if(msg === '' || activeGID.value === null)return - let key = gID2key[gID].key - msg = encryptString(key, msg, setting) - socket.send(JSON.stringify({ type: 'message',type1:'img', msg, gID })) -} - - -// 通过地址传参并加入传入的群组 -// function getQueryString(key) { -// var reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)', 'i'); -// var r = window.location.search.substring(1).match(reg); -// if (r != null) { -// return unescape(r[2]); -// } -// return 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')