From cf82967060d8c504d5697fbd47f16b7f5e683ffa Mon Sep 17 00:00:00 2001 From: xion Date: Mon, 12 May 2025 04:30:40 +0800 Subject: [PATCH] perf --- package.json | 4 +- pnpm-lock.yaml | 32 +++++++++---- src/module/logger.ts | 2 + src/module/proxy/ai-proxy.ts | 90 ++++++++++++++++++++++++------------ 4 files changed, 90 insertions(+), 38 deletions(-) create mode 100644 src/module/logger.ts diff --git a/package.json b/package.json index f64254b..5013855 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "author": "", "license": "ISC", "devDependencies": { + "@kevisual/logger": "^0.0.2", + "@kevisual/oss": "^0.0.7", "@rollup/plugin-commonjs": "^28.0.3", "@rollup/plugin-json": "^6.1.0", "@rollup/plugin-node-resolve": "^16.0.1", @@ -51,7 +53,7 @@ "@kevisual/permission": "^0.0.1", "@kevisual/query": "^0.0.17", "@kevisual/query-config": "^0.0.2", - "@kevisual/router": "0.0.13", + "@kevisual/router": "0.0.14", "@kevisual/use-config": "^1.0.15", "archiver": "^7.0.1", "busboy": "^1.6.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c79b918..f67d54d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: dependencies: '@kevisual/code-center-module': specifier: 0.0.18 - version: 0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.13)(@kevisual/use-config@1.0.15(dotenv@16.4.7))(ioredis@5.6.1)(pg@8.13.3)(sequelize@6.37.7(pg@8.13.3)) + version: 0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.14)(@kevisual/use-config@1.0.15(dotenv@16.4.7))(ioredis@5.6.1)(pg@8.13.3)(sequelize@6.37.7(pg@8.13.3)) '@kevisual/permission': specifier: ^0.0.1 version: 0.0.1 @@ -24,8 +24,8 @@ importers: specifier: ^0.0.2 version: 0.0.2(@kevisual/query@0.0.17(ws@8.18.0)(zod@3.24.2)) '@kevisual/router': - specifier: 0.0.13 - version: 0.0.13 + specifier: 0.0.14 + version: 0.0.14 '@kevisual/use-config': specifier: ^1.0.15 version: 1.0.15(dotenv@16.4.7) @@ -54,6 +54,12 @@ importers: specifier: ^6.37.7 version: 6.37.7(pg@8.13.3) devDependencies: + '@kevisual/logger': + specifier: ^0.0.2 + version: 0.0.2 + '@kevisual/oss': + specifier: ^0.0.7 + version: 0.0.7 '@rollup/plugin-commonjs': specifier: ^28.0.3 version: 28.0.3(rollup@4.40.2) @@ -125,6 +131,12 @@ packages: '@kevisual/load@0.0.6': resolution: {integrity: sha512-+3YTFehRcZ1haGel5DKYMUwmi5i6f2psyaPZlfkKU/cOXgkpwoG9/BEqPCnPjicKqqnksEpixVRkyHJ+5bjLVA==} + '@kevisual/logger@0.0.2': + resolution: {integrity: sha512-4NVdNsOHmMRg+OuZPoNNdI3p7jRII7lMJHRar1IoBck7fFIV7YGMNQirrrjk07MHv+Eh+U+uUljjgEWbse92RA==} + + '@kevisual/oss@0.0.7': + resolution: {integrity: sha512-saPN4A1CaGvSkFZzLE4zMsm+WwXi6Z97Yavz6koWFziuJIi/ay0793A4EDZ0iIpE9MMTRLYsuSQTUxDzsZV4Kg==} + '@kevisual/permission@0.0.1': resolution: {integrity: sha512-nSX2LzbPkU3YAMegbUFGU8tfmtFb7dcF5edqzm+gI6crcyCL1JzIB9HAYNEeEVIljLxuREwM/vVg9aFmF4cz9Q==} @@ -136,8 +148,8 @@ packages: '@kevisual/query@0.0.17': resolution: {integrity: sha512-WMvWM+3pNlPKNhoxPX9fldMp1tOeJrkRM/tXA4bvOnftIoX2yeI4v0wTpbGJXES/bLlo7OC2kV8SeKF0K6dnxQ==} - '@kevisual/router@0.0.13': - resolution: {integrity: sha512-raji8aKXr0jigmJVOKBXb5gpstiAuyoIDy9m6SyPf4lRjCU3pspVI1bpscOUCBlaPICo6TLzPQxXhyTvvvtdWw==} + '@kevisual/router@0.0.14': + resolution: {integrity: sha512-eHOSD6gzs/Ed9DDTq9VCEM3uw3Wh1vPFlyPo91cEe2rA44t8oJm8g905qmwHVUEjbo5s/rA306ZW/ub06jlNlg==} '@kevisual/use-config@1.0.15': resolution: {integrity: sha512-bLWdGMOPHgIKV4qY3U18cLoOKmSBL72K1wL0MneyEsqj9jRXoc98OMMyQm2/BlBddFTL1olOfByRET2DvwmWAA==} @@ -1446,10 +1458,10 @@ snapshots: '@kevisual/auth@1.0.5': {} - '@kevisual/code-center-module@0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.13)(@kevisual/use-config@1.0.15(dotenv@16.4.7))(ioredis@5.6.1)(pg@8.13.3)(sequelize@6.37.7(pg@8.13.3))': + '@kevisual/code-center-module@0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.14)(@kevisual/use-config@1.0.15(dotenv@16.4.7))(ioredis@5.6.1)(pg@8.13.3)(sequelize@6.37.7(pg@8.13.3))': dependencies: '@kevisual/auth': 1.0.5 - '@kevisual/router': 0.0.13 + '@kevisual/router': 0.0.14 '@kevisual/use-config': 1.0.15(dotenv@16.4.7) ioredis: 5.6.1 nanoid: 5.1.5 @@ -1466,6 +1478,10 @@ snapshots: dependencies: eventemitter3: 5.0.1 + '@kevisual/logger@0.0.2': {} + + '@kevisual/oss@0.0.7': {} + '@kevisual/permission@0.0.1': {} '@kevisual/query-config@0.0.2(@kevisual/query@0.0.17(ws@8.18.0)(zod@3.24.2))': @@ -1480,7 +1496,7 @@ snapshots: - ws - zod - '@kevisual/router@0.0.13': + '@kevisual/router@0.0.14': dependencies: path-to-regexp: 8.2.0 selfsigned: 2.4.1 diff --git a/src/module/logger.ts b/src/module/logger.ts new file mode 100644 index 0000000..c51b9c7 --- /dev/null +++ b/src/module/logger.ts @@ -0,0 +1,2 @@ +import { Logger } from '@kevisual/logger/node'; +export const logger = new Logger(); diff --git a/src/module/proxy/ai-proxy.ts b/src/module/proxy/ai-proxy.ts index 54ce0b3..785e820 100644 --- a/src/module/proxy/ai-proxy.ts +++ b/src/module/proxy/ai-proxy.ts @@ -6,17 +6,13 @@ import { UserPermission, Permission } from '@kevisual/permission'; import { getLoginUser } from '@/middleware/auth.ts'; import busboy from 'busboy'; import { getContentType } from '../get-content-type.ts'; - -const getAiProxy = async ( - req: IncomingMessage, - res: ServerResponse, - opts: { - createNotFoundPage: (msg?: string) => any; - }, -) => { +import { OssBase } from '@kevisual/oss'; +import { parseSearchValue } from '@kevisual/router/browser'; +// import { logger } from '@/module/logger.ts'; +const getAiProxy = async (req: IncomingMessage, res: ServerResponse, opts: ProxyOptions) => { const { createNotFoundPage } = opts; const _u = new URL(req.url, 'http://localhost'); - + const oss = opts.oss; const pathname = _u.pathname; const params = _u.searchParams; const password = params.get('p'); @@ -32,8 +28,8 @@ const getAiProxy = async ( owner = user; } try { - const stat = await minioClient.statObject(bucketName, objectName); - if (stat.size === 0) { + const stat = await oss.statObject(objectName); + if (!stat) { createNotFoundPage('Invalid proxy url'); return true; } @@ -57,7 +53,7 @@ const getAiProxy = async ( 'Content-Length': contentLength, etag, 'last-modified': lastModified, - 'file-name': fileName, + 'x-file-name': fileName, ...filterMetaData, }; @@ -90,11 +86,19 @@ export const getMetadata = (pathname: string) => { } return meta; }; -export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: { createNotFoundPage: (msg?: string) => any }) => { + +export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: ProxyOptions) => { const _u = new URL(req.url, 'http://localhost'); const pathname = _u.pathname; + const oss = opts.oss; const params = _u.searchParams; + const force = !!params.get('force'); + const hash = params.get('hash'); + let meta = parseSearchValue(params.get('meta'), { decode: true }); + if (!hash && !force) { + return opts?.createNotFoundPage?.('no hash'); + } let objectName = ''; let owner = ''; const { user, app } = getUserFromRequest(req); @@ -110,22 +114,48 @@ export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: if (loginUser?.tokenUser?.username !== owner) { return opts?.createNotFoundPage?.('no permission'); } - const bb = busboy({ headers: req.headers }); + const end = (data: any, message?: string, code = 200) => { + res.writeHead(code, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ code: code, data: data, message: message || 'success' })); + }; + if (!force) { + const check = await oss.checkObjectHash(objectName, hash); + if (check) { + return end({ success: true, hash }, '文件已存在'); + } + } + const bb = busboy({ + headers: req.headers, + limits: { + fileSize: 100 * 1024 * 1024, // 100MB + files: 1, + }, + }); + let fileProcessed = false; bb.on('file', async (name, file, info) => { + fileProcessed = true; try { - await minioClient.putObject(bucketName, objectName, file, undefined, { - ...getMetadata(pathname), - }); + await oss.putObject( + objectName, + file, + { + ...getMetadata(pathname), + ...meta, + }, + { check: true, isStream: true }, + ); end({ success: true, name, info }, '上传成功', 200); } catch (error) { end({ error: error }, '上传失败', 500); } }); - const end = (data: any, message?: string, code = 200) => { - res.writeHead(code, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ code: code, data: data, message: message || 'success' })); - }; - bb.on('finish', end); + + bb.on('finish', () => { + // 只有当没有文件被处理时才执行end + if (!fileProcessed) { + end({ success: false }, '没有接收到文件', 400); + } + }); bb.on('error', (err) => { console.error('Busboy 错误:', err); end({ error: err }, '文件解析失败', 500); @@ -133,13 +163,15 @@ export const postProxy = async (req: IncomingMessage, res: ServerResponse, opts: req.pipe(bb); }; -export const aiProxy = async ( - req: IncomingMessage, - res: ServerResponse, - opts: { - createNotFoundPage: (msg?: string) => any; - }, -) => { +type ProxyOptions = { + createNotFoundPage: (msg?: string) => any; + oss?: OssBase; +}; +export const aiProxy = async (req: IncomingMessage, res: ServerResponse, opts: ProxyOptions) => { + const oss = new OssBase({ bucketName, client: minioClient }); + if (!opts.oss) { + opts.oss = oss; + } if (req.method === 'POST') { return postProxy(req, res, opts); }