diff --git a/package.json b/package.json index d5d33a9..40045f2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "lowdb": "^1.0.0", "marked": "^7.0.4", "mitt": "^3.0.0", + "multer": "^1.4.5-lts.1", "picgo": "^1.5.8", "qrcode.vue": "^3.3.3", "shell-path": "2.1.0", @@ -54,6 +55,7 @@ "@types/inquirer": "^6.5.0", "@types/js-yaml": "^4.0.5", "@types/lowdb": "^1.0.9", + "@types/multer": "^1.4.12", "@types/node": "^16.10.2", "@types/request-promise-native": "^1.0.17", "@types/semver": "^7.3.8", diff --git a/src/main/server/index.ts b/src/main/server/index.ts index f8ac34a..66b5d00 100644 --- a/src/main/server/index.ts +++ b/src/main/server/index.ts @@ -4,9 +4,64 @@ import { handleResponse, ensureHTTPLink } from './utils' +import { dbPathDir } from 'apis/core/datastore/dbChecker' import picgo from '@core/picgo' import logger from '@core/picgo/logger' import axios from 'axios' +import multer from 'multer' +import path from 'path' +import fs from 'fs' + +// 扩展原生 IncomingMessage,添加 multer 字段 +declare module 'http' { + interface IncomingMessage { + files?: { + path: string; + originalname: string; + mimetype: string; + size: number; + [key: string]: any; + }[]; + body?: any; + } +} + +// Multer 中间件适配器类型 +export type MulterMiddleware = ( + req: http.IncomingMessage, + res: http.ServerResponse, + callback: (error?: any) => void +) => void; + +// 扩展 multer 函数的返回类型 +declare module 'multer' { + interface Multer { + array(fieldname: string, maxCount?: number): MulterMiddleware; + single(fieldname: string): MulterMiddleware; + fields(fields: { name: string; maxCount?: number }[]): MulterMiddleware; + none(): MulterMiddleware; + } +} + +const STORE_PATH = dbPathDir() +const formImagesPath = path.join(STORE_PATH, 'picgo-form-images') +if (!fs.existsSync(formImagesPath)) { + fs.mkdirSync(formImagesPath, { recursive: true }) +} + +// 使用 multer 处理表单文件 +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, formImagesPath) + }, + filename: function (req, file, cb) { + cb(null, Date.now() + path.extname(file.originalname)) + } +}) + +const upload = multer({ + storage +}) class Server { private httpServer: http.Server @@ -56,6 +111,51 @@ class Server { success: false } }) + } else if (request.headers['content-type'] && request.headers['content-type'].includes('multipart/form-data')) { + logger.info('[PicGo Server] handling file upload') + upload.array('files')(request, response, async (err) => { + if (err) { + logger.error('[PicGo Server] file upload error', err) + return handleResponse({ + response, + body: { + success: false, + message: 'File upload failed' + } + }) + } + + try { + const files = (request).files + if (!files || files.length === 0) { + return handleResponse({ + response, + body: { + success: false, + message: 'No files were uploaded' + } + }) + } + + const filePaths = files.map((file) => file.path) + logger.info('[PicGo Server] files uploaded: ' + filePaths.join(', ')) + + const handler = routers.getHandler(request.url!) + handler!({ + list: filePaths, + response + }) + } catch (err: any) { + logger.error('[PicGo Server] process upload files error', err) + handleResponse({ + response, + body: { + success: false, + message: 'Failed to process uploaded files' + } + }) + } + }) } else { let body: string = '' let postObj: IObj diff --git a/yarn.lock b/yarn.lock index f72ed56..8bcb3e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2906,6 +2906,13 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/multer@^1.4.12": + version "1.4.12" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.12.tgz#da67bd0c809f3a63fe097c458c0d4af1fea50ab7" + integrity sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg== + dependencies: + "@types/express" "*" + "@types/node@*", "@types/node@^14.6.2", "@types/node@^16.10.2": version "16.11.18" resolved "https://registry.npmjs.org/@types/node/-/node-16.11.18.tgz#39ed7c52943b0cee6d7299b717707bd51b1f90b9" @@ -3950,6 +3957,11 @@ app-builder-lib@23.3.3: tar "^6.1.11" temp-file "^3.4.0" +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw== + arch@^2.1.1: version "2.2.0" resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" @@ -4472,6 +4484,13 @@ builtins@^5.0.1: dependencies: semver "^7.0.0" +busboy@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" + integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== + dependencies: + streamsearch "^1.1.0" + bytes@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -5019,7 +5038,7 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.6.2: +concat-stream@^1.5.2, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -9344,6 +9363,19 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multer@^1.4.5-lts.1: + version "1.4.5-lts.1" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.5-lts.1.tgz#803e24ad1984f58edffbc79f56e305aec5cfd1ac" + integrity sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ== + dependencies: + append-field "^1.0.0" + busboy "^1.0.0" + concat-stream "^1.5.2" + mkdirp "^0.5.4" + object-assign "^4.1.1" + type-is "^1.6.4" + xtend "^4.0.0" + multicast-dns@^7.2.5: version "7.2.5" resolved "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" @@ -9539,7 +9571,7 @@ number-is-nan@^1.0.0: resolved "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= -object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -11580,6 +11612,11 @@ stream-browserify@3.0.0: inherits "~2.0.4" readable-stream "^3.5.0" +streamsearch@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" + integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -12210,7 +12247,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.18: +type-is@^1.6.4, type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==