diff --git a/api/useDeleteApi.js b/api/useDeleteApi.js new file mode 100644 index 0000000..d4a7836 --- /dev/null +++ b/api/useDeleteApi.js @@ -0,0 +1,46 @@ +import { api, staticPath } from "../config.js"; +import path from "path"; +import useFsOprateEventCenter from "../hooks/useFsOprateEventCenter.js"; +import fs from 'fs' + +const fsOprateEventCenter = useFsOprateEventCenter() + +function deletePath(targetPath) { + return new Promise((resolve, reject) => { + fs.stat(targetPath, (err, stats) => { + if (err) { + console.log(err) + return reject(err); // 如果获取状态失败,返回错误 + } + + if (stats.isFile()) { + // 如果是文件,直接删除 + fs.unlink(targetPath, (err) => { + if (err) return reject(err); + resolve(); // 删除成功 + }); + // console.log('delete file') + } else if (stats.isDirectory()) { + // 如果是文件夹,递归删除 + fs.rmdir(targetPath, { recursive: true }, (err) => { + if (err) return reject(err); + resolve(); // 删除成功 + }); + // console.log('delete directory') + } else { + // console.log('unknow file') + // 其他类型(如符号链接等),可以根据需要处理 + reject('unknow file'); // 这里选择不处理其他类型 + } + }); + }); +} + +export default function useDeleteApi(app) { + app.post(api.delete, (req, res) => { + const decodedPath = decodeURIComponent(req.path).substring(api.delete.length - 1); + const targetPath = path.join(staticPath, decodedPath); + const eventId = fsOprateEventCenter.push(deletePath(targetPath)) + res.send({ eventId }); + }); +} diff --git a/api/useGetApi.js b/api/useGetApi.js index 64f18f7..c914630 100644 --- a/api/useGetApi.js +++ b/api/useGetApi.js @@ -1,7 +1,9 @@ import express from "express"; import path from "path"; import fs from "fs"; -import { staticPath, api_get } from "../config.js"; +import { staticPath, api } from "../config.js"; + +const api_get = api.get export default function useGetApi(app) { // 不存在文件与文件夹同名的问题 diff --git a/api/useQueryApi.js b/api/useQueryApi.js new file mode 100644 index 0000000..3da6003 --- /dev/null +++ b/api/useQueryApi.js @@ -0,0 +1,11 @@ +import { api } from "../config.js"; +import useFsOprateEventCenter from "../hooks/useFsOprateEventCenter.js"; + +const fsOprateEventCenter = useFsOprateEventCenter(); + +export default function useQueryApi(app) { + app.get(api.query, (req, res) => { + const id = req.query.id; + res.json(fsOprateEventCenter.query(id)); + }); +} diff --git a/api/useUploadApi.js b/api/useUploadApi.js index f83ee10..eb86476 100644 --- a/api/useUploadApi.js +++ b/api/useUploadApi.js @@ -1,15 +1,20 @@ import path from "path"; import fs from "fs"; import multer from "multer"; -import { api_upload, staticPath } from "../config.js"; +import { api, staticPath } from "../config.js"; + +const api_upload = api.upload const storage = multer.memoryStorage(); const upload = multer({ storage: storage }).single('file'); export default function useUploadApi(app) { app.post(api_upload, upload, (req, res) => { - const decodedPath = decodeURIComponent(req.path).substring(8); - const savePath = path.join(staticPath, decodedPath, req.file.originalname) + // 必须这样传,不然中文乱码 + const fileName = req.body.fileName + + const decodedPath = decodeURIComponent(req.path).substring(api_upload.length - 1); + const savePath = path.join(staticPath, decodedPath, fileName) // 覆盖写or追加写 const oprateType = req.body.oprateType === 'write' ? 'write' : 'append' const data = req.file.buffer diff --git a/config.js b/config.js index d10463b..f7984e4 100644 --- a/config.js +++ b/config.js @@ -4,7 +4,11 @@ import path from "path"; export const staticPath = path.join("./static"); export const port = 17705; -export const api_get = `/${"get"}`; -export const api_upload = `/${"upload"}/*`; +export const api = { + get: `/${"get"}`, + upload: `/${"upload"}/*`, + delete: `/${"delete"}/*`, + query: `/${"query"}` +} -export default { staticPath, port, api_get, api_upload } \ No newline at end of file +export default { staticPath, port, api } \ No newline at end of file diff --git a/hooks/generateId.js b/hooks/generateId.js new file mode 100644 index 0000000..e3b9066 --- /dev/null +++ b/hooks/generateId.js @@ -0,0 +1,11 @@ + +/** + * 生成一个id,不唯一的几率比因为我代码烂的几率低 + * @param length 生成id的长度 + * @returns {number} + */ +export default function generateId(length) { + length || (length = 16); + let id = Math.ceil(Math.random() * 10 ** 16); + return id +} diff --git a/hooks/useFsOprateEventCenter.js b/hooks/useFsOprateEventCenter.js new file mode 100644 index 0000000..aa73bf1 --- /dev/null +++ b/hooks/useFsOprateEventCenter.js @@ -0,0 +1,79 @@ +import generateId from "./generateId.js" + +// 一个文件操作事件 +class FsOprateEvent { + constructor(event){ + this.status = 'progress' + this.uid = generateId() + event.then(()=>{ + this.status = 'success' + }).catch((e)=>{ + this.failedEvent = e + this.status = 'failed' + }) + } + /** + * 事件状态,分为progress, success, failed + */ + failedEvent + status + uid +} + +class FsOprateEventCenter { + constructor(){ + this.eventMap = {} + } + eventMap + /** + * 推送一个事件,返回这个事件的id,后续可以通过这个id查询这个事件的状态 + * @param {Promise} event + * @returns {number} 事件id + */ + push(event){ + const ev = new FsOprateEvent(event) + const id = ev.uid + this.eventMap[id] = { + event: ev, + // 每小时自查询一下状态,防止一直没查询导致内存泄漏 + timer: setInterval(()=>{ + this.query(id) + }, 1000*60*60) + } + return id + } + /** + * 推送一个事件,返回这个事件的id,后续可以通过这个id查询这个事件的状态 + * 当查询到这个事件已经完成的时候,将这个事件移除出事件表(eventMap) + * @param {number} id 事件id + * @returns {{status: 'success' | 'failed' | 'progress' | 'unknow'; result?: any}} 事件id + */ + query(id){ + let status + if(!this.eventMap[id]){ + status = 'unknow' + } + else{ + status = this.eventMap[id].event.status + } + const res = {status} + if( ['success', 'failed'].includes(status) ){ + if(status === 'failed'){ + res.result = this.eventMap[id].event.failedEvent + } + clearInterval(this.eventMap[id].timer) + this.eventMap[id] = undefined + } + return res + } +} + +const fsOprateEventCenter = new FsOprateEventCenter() + +/** + * 除了上传以外的所有文件操作都统一通过这个进行管理 + * @returns {fsOprateEventCenter} + */ +export default function useFsOprateEventCenter(){ + return fsOprateEventCenter +} diff --git a/index.js b/index.js index e0df84e..1175330 100644 --- a/index.js +++ b/index.js @@ -2,23 +2,25 @@ import express from "express"; import os from "os"; import cors from "cors"; import fs from "fs"; -import useGetApi from "./api/useGetApi.js"; import Config from "./config.js"; +import useGetApi from "./api/useGetApi.js"; import useUploadApi from "./api/useUploadApi.js"; +import useDeleteApi from "./api/useDeleteApi.js"; +import useQueryApi from "./api/useQueryApi.js"; - -const { staticPath, port, api_get } = Config; +const { staticPath, port, api } = Config; if (!fs.existsSync(staticPath)) { fs.mkdirSync(staticPath); } - const app = express(); app.use(cors()); useGetApi(app); useUploadApi(app); +useDeleteApi(app); +useQueryApi(app); app.listen(port, "::", () => { const interfaces = os.networkInterfaces(); @@ -31,7 +33,7 @@ app.listen(port, "::", () => { console.log(`以下地址访问文件/文件夹:`); for (const name of Object.keys(interfaces)) { for (const iface of interfaces[name]) { - iface.family === "IPv6" ? console.log(`http://[${iface.address}]:${port}${api_get}`) : console.log(`http://${iface.address}:${port}${api_get}`); + iface.family === "IPv6" ? console.log(`http://[${iface.address}]:${port}${api.get}`) : console.log(`http://${iface.address}:${port}${api.get}`); } } }); diff --git a/package.json b/package.json index 7f29ac9..c4d225d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "type": "module", "scripts": { - "dev": "node index.js", + "dev": "ts-node index.js", "dev:bun": "bun index.js", "serve": "node dist/index.min.js", "serve:bun": "bun dist/index.min.js", @@ -17,7 +17,8 @@ "dependencies": { "cors": "^2.8.5", "express": "^4.19.2", - "multer": "1.4.5-lts.1" + "multer": "1.4.5-lts.1", + "ts-node": "^10.9.2" }, "devDependencies": { "@vercel/ncc": "^0.38.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a43b488..29eae9c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: multer: specifier: 1.4.5-lts.1 version: 1.4.5-lts.1 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.2.0)(typescript@5.5.4) devDependencies: '@vercel/ncc': specifier: ^0.38.1 @@ -27,6 +30,35 @@ importers: packages: + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/node@22.2.0': + resolution: {integrity: sha512-bm6EG6/pCpkxDf/0gDNDdtDILMOHgaQBVOJGdwsqClnxA3xL6jtMv76rLBc006RVMWbmaf0xbmom4Z/5o2nRkQ==} + '@vercel/ncc@0.38.1': resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} hasBin: true @@ -35,9 +67,21 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + engines: {node: '>=0.4.0'} + + acorn@8.12.1: + resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + engines: {node: '>=0.4.0'} + hasBin: true + append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} @@ -86,6 +130,9 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -106,6 +153,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -187,6 +238,9 @@ packages: isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -316,6 +370,20 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -323,11 +391,19 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} + engines: {node: '>=14.17'} + hasBin: true + uglify-js@3.19.1: resolution: {integrity: sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==} engines: {node: '>=0.8.0'} hasBin: true + undici-types@6.13.0: + resolution: {integrity: sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -339,6 +415,9 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -347,8 +426,37 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + snapshots: + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/node@22.2.0': + dependencies: + undici-types: 6.13.0 + '@vercel/ncc@0.38.1': {} accepts@1.3.8: @@ -356,8 +464,16 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-walk@8.3.3: + dependencies: + acorn: 8.12.1 + + acorn@8.12.1: {} + append-field@1.0.0: {} + arg@4.1.3: {} + array-flatten@1.1.1: {} body-parser@1.20.2: @@ -417,6 +533,8 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + create-require@1.1.1: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -431,6 +549,8 @@ snapshots: destroy@1.2.0: {} + diff@4.0.2: {} + ee-first@1.1.1: {} encodeurl@1.0.2: {} @@ -541,6 +661,8 @@ snapshots: isarray@1.0.0: {} + make-error@1.3.6: {} + media-typer@0.3.0: {} merge-descriptors@1.0.1: {} @@ -680,6 +802,24 @@ snapshots: toidentifier@1.0.1: {} + ts-node@10.9.2(@types/node@22.2.0)(typescript@5.5.4): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.2.0 + acorn: 8.12.1 + acorn-walk: 8.3.3 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.5.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -687,14 +827,22 @@ snapshots: typedarray@0.0.6: {} + typescript@5.5.4: {} + uglify-js@3.19.1: {} + undici-types@6.13.0: {} + unpipe@1.0.0: {} util-deprecate@1.0.2: {} utils-merge@1.0.1: {} + v8-compile-cache-lib@3.0.1: {} + vary@1.1.2: {} xtend@4.0.2: {} + + yn@3.1.1: {}